Consolidated the Multitargeting Analysis and Population Analysis modules. Addressed various bug fixes to improve functionality and user experience.
If the Multitargeting Analysis and Population Analysis modules are consolidated, users will be able to use these functionalities in CASPER.
If the bugs are resolved, users will be able to operate the program without experiencing crashes.
Special thanks to David for stress-testing the program last week. The following issues have been resolved:
My next step is to address any bugs that David may encounter during testing. I’ll focus on stabilizing the modules with outstanding issues and proceed with the implementation of the Microbiome Analysis module.
|
@@ -1,45 +1,113 @@
|
|
| 1 |
from models.FindTargetsModel import FindTargetsModel
|
| 2 |
from views.FindTargetsView import FindTargetsView
|
| 3 |
from PyQt6.QtWidgets import QMessageBox
|
|
|
|
| 4 |
|
| 5 |
class FindTargetsController:
|
| 6 |
def __init__(self, global_settings):
|
| 7 |
self.global_settings = global_settings
|
| 8 |
-
self.model =
|
| 9 |
-
self.view =
|
| 10 |
-
self._connect_signals()
|
| 11 |
self.organism = None
|
| 12 |
self.endonuclease = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
def _connect_signals(self):
|
| 15 |
-
|
|
|
|
|
|
|
| 16 |
|
| 17 |
def find_targets(self, input_data):
|
|
|
|
| 18 |
try:
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
self.organism = input_data['organism']
|
| 21 |
self.endonuclease = input_data['endonuclease']
|
|
|
|
|
|
|
| 22 |
results = self.model.find_targets(input_data)
|
| 23 |
-
self.global_settings.logger.debug(f"
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
except Exception as e:
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
|
| 34 |
def view_targets(self):
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from models.FindTargetsModel import FindTargetsModel
|
| 2 |
from views.FindTargetsView import FindTargetsView
|
| 3 |
from PyQt6.QtWidgets import QMessageBox
|
| 4 |
+
from PyQt6.QtCore import QTimer
|
| 5 |
|
| 6 |
class FindTargetsController:
|
| 7 |
def __init__(self, global_settings):
|
| 8 |
self.global_settings = global_settings
|
| 9 |
+
self.model = None
|
| 10 |
+
self.view = None
|
|
|
|
| 11 |
self.organism = None
|
| 12 |
self.endonuclease = None
|
| 13 |
+
self._input_data = None
|
| 14 |
+
self._current_annotation_file = None
|
| 15 |
+
|
| 16 |
+
# Connect to annotation file changes
|
| 17 |
+
self.global_settings.annotation_file_changed.connect(self._on_annotation_file_changed)
|
| 18 |
+
|
| 19 |
+
def _on_annotation_file_changed(self, new_annotation_file):
|
| 20 |
+
"""Handle annotation file changes by reprocessing data"""
|
| 21 |
+
try:
|
| 22 |
+
self.global_settings.logger.debug(f"FindTargetsController received new annotation file: {new_annotation_file}")
|
| 23 |
+
self._current_annotation_file = new_annotation_file
|
| 24 |
+
|
| 25 |
+
# Clear existing view and model
|
| 26 |
+
self.view = None
|
| 27 |
+
self.model = None
|
| 28 |
+
|
| 29 |
+
except Exception as e:
|
| 30 |
+
self.global_settings.logger.error(f"Error handling annotation file change: {str(e)}")
|
| 31 |
|
| 32 |
def _connect_signals(self):
|
| 33 |
+
"""Connect view signals"""
|
| 34 |
+
if self.view:
|
| 35 |
+
self.view.push_button_view_targets.clicked.connect(self.view_targets)
|
| 36 |
|
| 37 |
def find_targets(self, input_data):
|
| 38 |
+
"""Initialize view and process input data"""
|
| 39 |
try:
|
| 40 |
+
# Get current annotation file
|
| 41 |
+
current_annotation = self.global_settings.get_current_annotation_file()
|
| 42 |
+
input_data['annotation_file'] = current_annotation
|
| 43 |
+
self._current_annotation_file = current_annotation
|
| 44 |
+
|
| 45 |
+
# Always create new instances
|
| 46 |
+
self.model = FindTargetsModel(self.global_settings)
|
| 47 |
+
self.view = FindTargetsView(self.global_settings)
|
| 48 |
+
self._connect_signals()
|
| 49 |
+
|
| 50 |
+
self._input_data = input_data
|
| 51 |
+
|
| 52 |
+
# Find existing Find Targets tab
|
| 53 |
+
main_window = self.global_settings.main_window
|
| 54 |
+
existing_tab = main_window.find_tab_by_title("Find Targets")
|
| 55 |
+
|
| 56 |
+
if existing_tab:
|
| 57 |
+
# Remove the existing tab
|
| 58 |
+
tab_index = main_window.view.tab_widget.indexOf(existing_tab)
|
| 59 |
+
main_window.view.tab_widget.removeTab(tab_index)
|
| 60 |
+
|
| 61 |
+
# Process data and create new tab
|
| 62 |
+
self._process_input_data(input_data)
|
| 63 |
+
|
| 64 |
+
except Exception as e:
|
| 65 |
+
self.global_settings.logger.error(f"Error in find_targets: {str(e)}")
|
| 66 |
+
raise
|
| 67 |
+
|
| 68 |
+
def _process_input_data(self, input_data):
|
| 69 |
+
"""Process input data and update view"""
|
| 70 |
+
try:
|
| 71 |
+
if not self.view:
|
| 72 |
+
return
|
| 73 |
+
|
| 74 |
+
self.global_settings.logger.debug(f"FindTargetsController processing input data: {input_data}")
|
| 75 |
self.organism = input_data['organism']
|
| 76 |
self.endonuclease = input_data['endonuclease']
|
| 77 |
+
|
| 78 |
+
# Get new results
|
| 79 |
results = self.model.find_targets(input_data)
|
| 80 |
+
self.global_settings.logger.debug(f"Found {len(results) if results else 0} targets")
|
| 81 |
+
|
| 82 |
+
# Update view with new results
|
| 83 |
+
if results:
|
| 84 |
+
self.view.display_results(results)
|
| 85 |
+
|
| 86 |
+
# Add new tab with updated view
|
| 87 |
+
main_window = self.global_settings.main_window
|
| 88 |
+
main_window.open_new_tab("Find Targets", self)
|
| 89 |
+
|
| 90 |
except Exception as e:
|
| 91 |
+
self.global_settings.logger.error(f"Error processing input data: {str(e)}")
|
| 92 |
+
if self.view:
|
| 93 |
+
QMessageBox.critical(self.view, "Error", f"An error occurred while processing data: {str(e)}")
|
| 94 |
|
| 95 |
def view_targets(self):
|
| 96 |
+
"""Handle view targets button click"""
|
| 97 |
+
try:
|
| 98 |
+
if not self.view:
|
| 99 |
+
return
|
| 100 |
+
|
| 101 |
+
selected_targets = self.view.get_selected_targets()
|
| 102 |
+
if not selected_targets:
|
| 103 |
+
QMessageBox.warning(self.view, "No Selection", "Please select targets to view.")
|
| 104 |
+
return
|
| 105 |
+
|
| 106 |
+
view_targets_controller = self.global_settings.get_view_targets_window()
|
| 107 |
+
view_targets_controller.load_targets(selected_targets, self.organism, self.endonuclease)
|
| 108 |
+
self.global_settings.main_window.open_new_tab("View Targets", view_targets_controller)
|
| 109 |
+
|
| 110 |
+
except Exception as e:
|
| 111 |
+
self.global_settings.logger.error(f"Error in view_targets: {str(e)}")
|
| 112 |
+
if self.view:
|
| 113 |
+
QMessageBox.critical(self.view, "Error", f"An error occurred while viewing targets: {str(e)}")
|
|
@@ -78,6 +78,9 @@ class HomeWindowController:
|
|
| 78 |
self.view.push_button_find_targets.clicked.connect(self.gather_settings)
|
| 79 |
self.view.push_button_view_targets.clicked.connect(self.view_results)
|
| 80 |
self.view.push_button_generate_library.clicked.connect(self.prep_gen_lib)
|
|
|
|
|
|
|
|
|
|
| 81 |
except Exception as e:
|
| 82 |
show_error(self.global_settings, "Error setting up connections in HomeWindowController", str(e))
|
| 83 |
|
|
@@ -134,17 +137,27 @@ class HomeWindowController:
|
|
| 134 |
|
| 135 |
def open_multitargeting_analysis_module(self):
|
| 136 |
try:
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
except Exception as e:
|
| 141 |
show_error(self.global_settings, "Error in open_multitargeting_analysis_widget() in Home", str(e))
|
| 142 |
|
| 143 |
def open_population_analysis_module(self):
|
| 144 |
try:
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
except Exception as e:
|
| 149 |
show_error(self.global_settings, "Error in open_population_analysis_widget() in Home", str(e))
|
| 150 |
|
|
@@ -198,4 +211,11 @@ class HomeWindowController:
|
|
| 198 |
|
| 199 |
def get_annotation_files(self):
|
| 200 |
return self.model.get_annotation_files()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
|
|
|
|
| 78 |
self.view.push_button_find_targets.clicked.connect(self.gather_settings)
|
| 79 |
self.view.push_button_view_targets.clicked.connect(self.view_results)
|
| 80 |
self.view.push_button_generate_library.clicked.connect(self.prep_gen_lib)
|
| 81 |
+
|
| 82 |
+
# Add connection for annotation file changes
|
| 83 |
+
self.view.combo_box_local_annotation_files.currentTextChanged.connect(self._on_annotation_file_changed)
|
| 84 |
except Exception as e:
|
| 85 |
show_error(self.global_settings, "Error setting up connections in HomeWindowController", str(e))
|
| 86 |
|
|
|
|
| 137 |
|
| 138 |
def open_multitargeting_analysis_module(self):
|
| 139 |
try:
|
| 140 |
+
main_window = self.global_settings.main_window
|
| 141 |
+
existing_tab = main_window.find_tab_by_title("Multitargeting Analysis")
|
| 142 |
+
if existing_tab:
|
| 143 |
+
main_window.view.tab_widget.setCurrentWidget(existing_tab)
|
| 144 |
+
main_window._resize_for_tab("Multitargeting Analysis")
|
| 145 |
+
else:
|
| 146 |
+
multitargeting_controller = self.global_settings.get_multitargeting_window()
|
| 147 |
+
main_window.open_new_tab("Multitargeting Analysis", multitargeting_controller)
|
| 148 |
except Exception as e:
|
| 149 |
show_error(self.global_settings, "Error in open_multitargeting_analysis_widget() in Home", str(e))
|
| 150 |
|
| 151 |
def open_population_analysis_module(self):
|
| 152 |
try:
|
| 153 |
+
main_window = self.global_settings.main_window
|
| 154 |
+
existing_tab = main_window.find_tab_by_title("Population Analysis")
|
| 155 |
+
if existing_tab:
|
| 156 |
+
main_window.view.tab_widget.setCurrentWidget(existing_tab)
|
| 157 |
+
main_window._resize_for_tab("Population Analysis")
|
| 158 |
+
else:
|
| 159 |
+
population_analysis_controller = self.global_settings.get_population_analysis_window()
|
| 160 |
+
main_window.open_new_tab("Population Analysis", population_analysis_controller)
|
| 161 |
except Exception as e:
|
| 162 |
show_error(self.global_settings, "Error in open_population_analysis_widget() in Home", str(e))
|
| 163 |
|
|
|
|
| 211 |
|
| 212 |
def get_annotation_files(self):
|
| 213 |
return self.model.get_annotation_files()
|
| 214 |
+
|
| 215 |
+
def get_annotation_file(self):
|
| 216 |
+
return self.view.get_annotation_file()
|
| 217 |
+
|
| 218 |
+
def _on_annotation_file_changed(self, new_file):
|
| 219 |
+
"""Handle changes to the annotation file selection"""
|
| 220 |
+
self.global_settings.set_current_annotation_file(new_file)
|
| 221 |
|
|
@@ -17,14 +17,12 @@ class MainWindowController:
|
|
| 17 |
self.tab_widgets = {} # Store references to tab widgets
|
| 18 |
self.startup_controller = None
|
| 19 |
self.is_first_time_startup = self.global_settings.is_first_time_startup
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
"View Targets": QSize(1500, 700),
|
| 27 |
-
}
|
| 28 |
self.current_tab = None
|
| 29 |
|
| 30 |
try:
|
|
@@ -244,25 +242,16 @@ class MainWindowController:
|
|
| 244 |
self.view.tab_widget.currentChanged.connect(self._on_tab_changed)
|
| 245 |
|
| 246 |
def _resize_for_tab(self, title):
|
| 247 |
-
if title
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
self.view.setFixedSize(new_size)
|
| 252 |
-
self.view.setWindowFlags(self.view.windowFlags() & ~Qt.WindowType.WindowMaximizeButtonHint)
|
| 253 |
-
else:
|
| 254 |
-
# For other tabs, allow resizing but set a minimum size
|
| 255 |
-
self.view.setMinimumSize(QSize(400, 300)) # Set a reasonable minimum size
|
| 256 |
-
self.view.setMaximumSize(QtCore.QSize(16777215, 16777215))
|
| 257 |
-
self.view.setWindowFlags(self.view.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint)
|
| 258 |
-
|
| 259 |
-
# Resize to the specified size for the tab
|
| 260 |
-
self.view.resize(new_size)
|
| 261 |
else:
|
| 262 |
-
#
|
| 263 |
-
self.view.setMinimumSize(QSize(400, 300))
|
| 264 |
self.view.setMaximumSize(QtCore.QSize(16777215, 16777215))
|
| 265 |
self.view.setWindowFlags(self.view.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint)
|
|
|
|
| 266 |
|
| 267 |
# Ensure window flags are updated
|
| 268 |
self.view.show()
|
|
@@ -349,16 +338,15 @@ class MainWindowController:
|
|
| 349 |
return None
|
| 350 |
|
| 351 |
def _on_tab_changed(self, index):
|
| 352 |
-
# Save the current
|
| 353 |
-
if self.current_tab:
|
| 354 |
current_size = self.view.size()
|
| 355 |
if current_size.width() >= 400 and current_size.height() >= 300:
|
| 356 |
-
|
|
|
|
| 357 |
|
| 358 |
-
# Get the new tab title
|
| 359 |
new_tab_title = self.view.tab_widget.tabText(index)
|
| 360 |
-
|
| 361 |
-
# Resize for the new tab
|
| 362 |
self._resize_for_tab(new_tab_title)
|
| 363 |
|
| 364 |
def close_new_genome_and_switch_to_home(self):
|
|
@@ -391,3 +379,5 @@ class MainWindowController:
|
|
| 391 |
show_error(self.global_settings, "Error switching to Home tab", str(e))
|
| 392 |
|
| 393 |
|
|
|
|
|
|
|
|
|
| 17 |
self.tab_widgets = {} # Store references to tab widgets
|
| 18 |
self.startup_controller = None
|
| 19 |
self.is_first_time_startup = self.global_settings.is_first_time_startup
|
| 20 |
+
|
| 21 |
+
# Single shared size for all regular tabs
|
| 22 |
+
self.shared_tab_size = QSize(850, 850)
|
| 23 |
+
# Separate size only for startup
|
| 24 |
+
self.startup_size = QSize(750, 550)
|
| 25 |
+
|
|
|
|
|
|
|
| 26 |
self.current_tab = None
|
| 27 |
|
| 28 |
try:
|
|
|
|
| 242 |
self.view.tab_widget.currentChanged.connect(self._on_tab_changed)
|
| 243 |
|
| 244 |
def _resize_for_tab(self, title):
|
| 245 |
+
if title == "Startup":
|
| 246 |
+
# For Startup tab, set fixed size and disable maximize button
|
| 247 |
+
self.view.setFixedSize(self.startup_size)
|
| 248 |
+
self.view.setWindowFlags(self.view.windowFlags() & ~Qt.WindowType.WindowMaximizeButtonHint)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
else:
|
| 250 |
+
# For all other tabs, use the shared size and allow resizing
|
| 251 |
+
self.view.setMinimumSize(QSize(400, 300))
|
| 252 |
self.view.setMaximumSize(QtCore.QSize(16777215, 16777215))
|
| 253 |
self.view.setWindowFlags(self.view.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint)
|
| 254 |
+
self.view.resize(self.shared_tab_size)
|
| 255 |
|
| 256 |
# Ensure window flags are updated
|
| 257 |
self.view.show()
|
|
|
|
| 338 |
return None
|
| 339 |
|
| 340 |
def _on_tab_changed(self, index):
|
| 341 |
+
# Save the current size before switching if it's not the startup tab
|
| 342 |
+
if self.current_tab and self.current_tab != "Startup":
|
| 343 |
current_size = self.view.size()
|
| 344 |
if current_size.width() >= 400 and current_size.height() >= 300:
|
| 345 |
+
# Update shared size for all non-startup tabs
|
| 346 |
+
self.shared_tab_size = current_size
|
| 347 |
|
| 348 |
+
# Get the new tab title and resize
|
| 349 |
new_tab_title = self.view.tab_widget.tabText(index)
|
|
|
|
|
|
|
| 350 |
self._resize_for_tab(new_tab_title)
|
| 351 |
|
| 352 |
def close_new_genome_and_switch_to_home(self):
|
|
|
|
| 379 |
show_error(self.global_settings, "Error switching to Home tab", str(e))
|
| 380 |
|
| 381 |
|
| 382 |
+
|
| 383 |
+
|
|
@@ -1,67 +1,256 @@
|
|
| 1 |
from PyQt6.QtWidgets import QMainWindow
|
| 2 |
from views.MultitargetingWindowView import MultitargetingWindowView
|
| 3 |
from models.MultitargetingWindowModel import MultitargetingWindowModel
|
|
|
|
| 4 |
|
| 5 |
class MultitargetingWindowController(QMainWindow):
|
| 6 |
def __init__(self, global_settings):
|
| 7 |
super().__init__()
|
| 8 |
-
self.
|
| 9 |
-
self.
|
| 10 |
-
self._model = MultitargetingWindowModel(global_settings)
|
| 11 |
-
self.setCentralWidget(self._view)
|
| 12 |
-
self.setup_connections()
|
| 13 |
-
|
| 14 |
-
@property
|
| 15 |
-
def view(self):
|
| 16 |
-
return self._view
|
| 17 |
-
|
| 18 |
-
@property
|
| 19 |
-
def model(self):
|
| 20 |
-
return self._model
|
| 21 |
-
|
| 22 |
-
def show(self):
|
| 23 |
-
super().show()
|
| 24 |
-
|
| 25 |
-
def setup_connections(self):
|
| 26 |
-
# Connect signals from view to controller methods
|
| 27 |
-
self.view.comboBox_organism.currentIndexChanged.connect(self.update_endo_list)
|
| 28 |
-
self.view.comboBox_endo.currentIndexChanged.connect(self.load_data)
|
| 29 |
-
self.view.pushButton_load.clicked.connect(self.load_data)
|
| 30 |
-
# Add more connections as needed
|
| 31 |
-
|
| 32 |
-
def update_endo_list(self):
|
| 33 |
-
organism = self.view.comboBox_organism.currentText()
|
| 34 |
-
endos = self.model.get_endos_for_organism(organism)
|
| 35 |
-
self.view.comboBox_endo.clear()
|
| 36 |
-
self.view.comboBox_endo.addItems(endos)
|
| 37 |
-
|
| 38 |
-
def load_data(self):
|
| 39 |
-
organism = self.view.comboBox_organism.currentText()
|
| 40 |
-
endo = self.view.comboBox_endo.currentText()
|
| 41 |
-
self.model.set_files(organism, endo)
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
-
#
|
| 48 |
-
self.
|
| 49 |
-
self.
|
|
|
|
| 50 |
|
| 51 |
-
#
|
| 52 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
-
#
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from PyQt6.QtWidgets import QMainWindow
|
| 2 |
from views.MultitargetingWindowView import MultitargetingWindowView
|
| 3 |
from models.MultitargetingWindowModel import MultitargetingWindowModel
|
| 4 |
+
from utils.ui import show_error
|
| 5 |
|
| 6 |
class MultitargetingWindowController(QMainWindow):
|
| 7 |
def __init__(self, global_settings):
|
| 8 |
super().__init__()
|
| 9 |
+
self.settings = global_settings
|
| 10 |
+
self.logger = global_settings.get_logger()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
+
try:
|
| 13 |
+
self._model = MultitargetingWindowModel(global_settings)
|
| 14 |
+
self._view = MultitargetingWindowView(global_settings)
|
| 15 |
+
self.setCentralWidget(self._view)
|
| 16 |
+
|
| 17 |
+
self._init_ui()
|
| 18 |
+
self._setup_connections()
|
| 19 |
+
except Exception as e:
|
| 20 |
+
show_error(self.settings, "Error initializing MultitargetingWindowController", str(e))
|
| 21 |
+
|
| 22 |
+
def _init_ui(self):
|
| 23 |
+
"""Initialize UI components"""
|
| 24 |
+
try:
|
| 25 |
+
# Set up initial data
|
| 26 |
+
organisms = self._model.get_organisms()
|
| 27 |
+
|
| 28 |
+
self._view.combo_box_organism.clear()
|
| 29 |
+
self._view.combo_box_organism.addItems(organisms)
|
| 30 |
+
|
| 31 |
+
# If we have organisms, trigger the first one
|
| 32 |
+
if organisms:
|
| 33 |
+
self._on_organism_changed(0)
|
| 34 |
+
|
| 35 |
+
# Initialize plots
|
| 36 |
+
self._view.setup_plots()
|
| 37 |
+
|
| 38 |
+
except Exception as e:
|
| 39 |
+
self.logger.error(f"Error in _init_ui: {str(e)}")
|
| 40 |
+
show_error(self.settings, "Error", f"Failed to initialize UI: {str(e)}")
|
| 41 |
+
|
| 42 |
+
def _setup_connections(self):
|
| 43 |
+
"""Set up signal-slot connections"""
|
| 44 |
+
# Organism and endonuclease selection
|
| 45 |
+
self._view.combo_box_organism.currentIndexChanged.connect(self._on_organism_changed)
|
| 46 |
+
self._view.combo_box_endonuclease.currentIndexChanged.connect(self._on_endonuclease_changed)
|
| 47 |
|
| 48 |
+
# Buttons
|
| 49 |
+
self._view.push_button_analyze.clicked.connect(self._on_analyze_clicked)
|
| 50 |
+
self._view.push_button_statistics_overview.clicked.connect(self._on_statistics_overview_clicked)
|
| 51 |
+
self._view.tool_button_sql_settings.clicked.connect(self._on_sql_settings_clicked)
|
| 52 |
|
| 53 |
+
# Table selection
|
| 54 |
+
self._view.table_seeds.itemSelectionChanged.connect(self._on_seed_selected)
|
| 55 |
+
self._view.check_box_select_all.stateChanged.connect(self._on_select_all_changed)
|
| 56 |
+
|
| 57 |
+
def _on_organism_changed(self, index):
|
| 58 |
+
"""Handle organism selection change"""
|
| 59 |
+
try:
|
| 60 |
+
organism = self._view.combo_box_organism.currentText()
|
| 61 |
+
if not organism:
|
| 62 |
+
return
|
| 63 |
+
|
| 64 |
+
# Get endos for selected organism
|
| 65 |
+
endos = self._model.get_endos_for_organism(organism)
|
| 66 |
+
|
| 67 |
+
# Update endonuclease combo box
|
| 68 |
+
self._view.combo_box_endonuclease.clear()
|
| 69 |
+
self._view.combo_box_endonuclease.addItems(endos)
|
| 70 |
+
|
| 71 |
+
# Update file paths
|
| 72 |
+
if endos:
|
| 73 |
+
self._model.set_files(organism, endos[0])
|
| 74 |
+
|
| 75 |
+
except Exception as e:
|
| 76 |
+
self.logger.error(f"Error in _on_organism_changed: {str(e)}")
|
| 77 |
+
show_error(self.settings, "Error", f"Failed to update endonucleases: {str(e)}")
|
| 78 |
+
|
| 79 |
+
def _on_endonuclease_changed(self, index):
|
| 80 |
+
"""Handle endonuclease selection change"""
|
| 81 |
+
self._update_analysis_button_state()
|
| 82 |
+
|
| 83 |
+
def _on_analyze_clicked(self):
|
| 84 |
+
"""Handle analyze button click"""
|
| 85 |
+
try:
|
| 86 |
+
organism = self._view.combo_box_organism.currentText()
|
| 87 |
+
endo = self._view.combo_box_endonuclease.currentText()
|
| 88 |
+
|
| 89 |
+
if not organism or not endo:
|
| 90 |
+
show_error(self.settings, "Analysis Error", "Please select both an organism and an endonuclease.")
|
| 91 |
+
return
|
| 92 |
+
|
| 93 |
+
# Load data
|
| 94 |
+
try:
|
| 95 |
+
self._model.set_files(organism, endo)
|
| 96 |
+
except FileNotFoundError as e:
|
| 97 |
+
show_error(self.settings, "File Error",
|
| 98 |
+
f"Could not find required files for {organism} with {endo}. Please ensure the files exist.")
|
| 99 |
+
return
|
| 100 |
+
except ValueError as e:
|
| 101 |
+
show_error(self.settings, "Input Error", str(e))
|
| 102 |
+
return
|
| 103 |
+
|
| 104 |
+
seeds_data = self._model.get_repeats_data()
|
| 105 |
+
|
| 106 |
+
# Update UI
|
| 107 |
+
self._view.update_seeds_table(seeds_data)
|
| 108 |
+
self._update_plots()
|
| 109 |
+
|
| 110 |
+
except Exception as e:
|
| 111 |
+
show_error(self.settings, "Analysis Error", str(e))
|
| 112 |
+
|
| 113 |
+
def _on_statistics_overview_clicked(self):
|
| 114 |
+
"""Handle statistics overview button click"""
|
| 115 |
+
try:
|
| 116 |
+
stats = self._model.calculate_statistics()
|
| 117 |
+
self._show_statistics_dialog(stats)
|
| 118 |
+
except Exception as e:
|
| 119 |
+
show_error(self.settings, "Statistics Error", str(e))
|
| 120 |
+
|
| 121 |
+
def _on_sql_settings_clicked(self):
|
| 122 |
+
"""Handle SQL settings button click"""
|
| 123 |
+
try:
|
| 124 |
+
current_settings = self._model.get_sql_settings()
|
| 125 |
+
if self._show_sql_settings_dialog(current_settings):
|
| 126 |
+
new_settings = self._get_sql_settings_from_dialog()
|
| 127 |
+
self._model.update_sql_settings(new_settings)
|
| 128 |
+
except Exception as e:
|
| 129 |
+
show_error(self.settings, "SQL Settings Error", str(e))
|
| 130 |
+
|
| 131 |
+
def _on_seed_selected(self):
|
| 132 |
+
"""Handle seed selection in table"""
|
| 133 |
+
try:
|
| 134 |
+
selected_items = self._view.table_seeds.selectedItems()
|
| 135 |
+
if selected_items:
|
| 136 |
+
row = selected_items[0].row()
|
| 137 |
+
seed = self._view.table_seeds.item(row, 0).text()
|
| 138 |
+
|
| 139 |
+
# Get seed data
|
| 140 |
+
seed_data = self._model.get_seed_data(seed)
|
| 141 |
+
if not seed_data:
|
| 142 |
+
return
|
| 143 |
|
| 144 |
+
# Process seed data for visualization
|
| 145 |
+
kstats = self._model.get_kstats()
|
| 146 |
+
seed_data_processed, event_data = self._process_seed_data(seed_data, kstats)
|
| 147 |
+
|
| 148 |
+
# Update chromosome viewer
|
| 149 |
+
self._view.fill_chromosome_viewer(seed_data_processed, event_data)
|
| 150 |
+
|
| 151 |
+
# Update only the chromosome bar plot
|
| 152 |
+
chromosome_data = self._model.get_chro_bar_data(seed)
|
| 153 |
+
# Update only the chromosome plot, keep other plots unchanged
|
| 154 |
+
self._view._update_repeat_vs_chromosome_plot(chromosome_data)
|
| 155 |
+
|
| 156 |
+
except Exception as e:
|
| 157 |
+
self.logger.error(f"Error handling seed selection: {str(e)}")
|
| 158 |
+
show_error(self.settings, "Error", f"Failed to display seed data: {str(e)}")
|
| 159 |
+
|
| 160 |
+
def _process_seed_data(self, seed_data, kstats):
|
| 161 |
+
"""Process seed data for visualization"""
|
| 162 |
+
try:
|
| 163 |
+
seed_data_processed = {}
|
| 164 |
+
event_data = {}
|
| 165 |
+
|
| 166 |
+
for data in seed_data:
|
| 167 |
+
# Split chromosome and location strings into lists
|
| 168 |
+
chromos = [int(x) for x in data[0].split(',')]
|
| 169 |
+
locs = [int(x) for x in data[1].split(',')]
|
| 170 |
+
pams = data[2].split(',')
|
| 171 |
+
scores = data[3].split(',')
|
| 172 |
+
fives = data[4].split(',')
|
| 173 |
+
threes = data[5].split(',')
|
| 174 |
+
|
| 175 |
+
# Process each chromosome location
|
| 176 |
+
for i in range(len(chromos)):
|
| 177 |
+
chromo = chromos[i]
|
| 178 |
+
pos = locs[i]
|
| 179 |
+
|
| 180 |
+
# Normalize location
|
| 181 |
+
dir = "+" if pos >= 0 else "-"
|
| 182 |
+
normalized_location = abs(float(pos) / float(kstats[chromo - 1]))
|
| 183 |
+
|
| 184 |
+
# Store data
|
| 185 |
+
if chromo in seed_data_processed:
|
| 186 |
+
seed_data_processed[chromo].append(normalized_location)
|
| 187 |
+
event_data[chromo].append([
|
| 188 |
+
normalized_location,
|
| 189 |
+
pos,
|
| 190 |
+
fives[i] + threes[i],
|
| 191 |
+
pams[i],
|
| 192 |
+
scores[i],
|
| 193 |
+
dir
|
| 194 |
+
])
|
| 195 |
+
else:
|
| 196 |
+
seed_data_processed[chromo] = [normalized_location]
|
| 197 |
+
event_data[chromo] = [[
|
| 198 |
+
normalized_location,
|
| 199 |
+
pos,
|
| 200 |
+
fives[i] + threes[i],
|
| 201 |
+
pams[i],
|
| 202 |
+
scores[i],
|
| 203 |
+
dir
|
| 204 |
+
]]
|
| 205 |
+
|
| 206 |
+
return seed_data_processed, event_data
|
| 207 |
+
|
| 208 |
+
except Exception as e:
|
| 209 |
+
self.logger.error(f"Error processing seed data: {str(e)}")
|
| 210 |
+
raise
|
| 211 |
+
|
| 212 |
+
def _on_select_all_changed(self, state):
|
| 213 |
+
"""Handle select all checkbox state change"""
|
| 214 |
+
self._view.table_seeds.selectAll() if state else self._view.table_seeds.clearSelection()
|
| 215 |
+
|
| 216 |
+
def _update_analysis_button_state(self):
|
| 217 |
+
"""Update analyze button enabled state"""
|
| 218 |
+
has_organism = bool(self._view.organism_drop.currentText())
|
| 219 |
+
has_endo = bool(self._view.combo_box_endonuclease.currentText())
|
| 220 |
+
self._view.push_button_analyze.setEnabled(has_organism and has_endo)
|
| 221 |
|
| 222 |
+
# Clear any existing data if selection changes
|
| 223 |
+
if not (has_organism and has_endo):
|
| 224 |
+
self._view.table_seeds.setRowCount(0)
|
| 225 |
+
self._view.update_plots(None, None, None)
|
| 226 |
+
|
| 227 |
+
def _update_plots(self):
|
| 228 |
+
"""Update all plots with current data"""
|
| 229 |
+
try:
|
| 230 |
+
# Get repeats vs seeds data first
|
| 231 |
+
repeats_data = self._model.get_repeats_vs_seeds_data()
|
| 232 |
+
|
| 233 |
+
# Get sequences vs repeats data
|
| 234 |
+
sequences_data = self._model.get_seeds_vs_repeats_data()
|
| 235 |
+
|
| 236 |
+
# Update all plots at once
|
| 237 |
+
self._view.update_plots(repeats_data, sequences_data, None) # chromosome_data will be updated on seed selection
|
| 238 |
+
|
| 239 |
+
except Exception as e:
|
| 240 |
+
self.logger.error(f"Error in _update_plots: {str(e)}")
|
| 241 |
+
show_error(self.settings, "Plot Update Error", str(e))
|
| 242 |
+
|
| 243 |
+
def _show_statistics_dialog(self, stats):
|
| 244 |
+
"""Show statistics overview dialog"""
|
| 245 |
+
# Implement statistics dialog display
|
| 246 |
+
pass
|
| 247 |
+
|
| 248 |
+
def _show_sql_settings_dialog(self, current_settings):
|
| 249 |
+
"""Show SQL settings dialog"""
|
| 250 |
+
# Implement SQL settings dialog display
|
| 251 |
+
return False
|
| 252 |
+
|
| 253 |
+
def _get_sql_settings_from_dialog(self):
|
| 254 |
+
"""Get settings from SQL settings dialog"""
|
| 255 |
+
# Implement getting settings from dialog
|
| 256 |
+
return {}
|
|
@@ -18,18 +18,22 @@ class NCBIWindowController:
|
|
| 18 |
self.model = NCBIWindowModel(settings)
|
| 19 |
self.view = NCBIWindowView(settings)
|
| 20 |
|
|
|
|
|
|
|
|
|
|
| 21 |
self._init_ui()
|
| 22 |
-
self.setup_connections()
|
| 23 |
except Exception as e:
|
| 24 |
show_error(self.settings, "Error initializing NCBIWindowController", str(e))
|
| 25 |
|
| 26 |
def setup_connections(self):
|
|
|
|
| 27 |
try:
|
| 28 |
self.view.push_button_search.clicked.connect(self.search_ncbi)
|
| 29 |
self.view.push_button_download_files.clicked.connect(self.download_files_wrapper)
|
| 30 |
self.view.check_box_select_all_rows.clicked.connect(self.select_all_rows_in_table)
|
| 31 |
self.view.radio_button_collections_genbank.toggled.connect(self.is_checked_GenBank_radio_button)
|
| 32 |
-
|
|
|
|
| 33 |
except Exception as e:
|
| 34 |
self.logger.error(f"Error setting up connections: {str(e)}", exc_info=True)
|
| 35 |
show_error(self.settings, "Error setting up connections", str(e))
|
|
@@ -135,7 +139,9 @@ class NCBIWindowController:
|
|
| 135 |
self.on_thread_completed() # Increment completed threads for unavailable files
|
| 136 |
continue
|
| 137 |
|
| 138 |
-
downloader = DownloadThread(self, url, id, species_name, strain,
|
|
|
|
|
|
|
| 139 |
downloader.finished.connect(self.on_download_finished)
|
| 140 |
downloader.progress_updated.connect(self.update_progress)
|
| 141 |
downloader.status_updated.connect(self.update_status)
|
|
@@ -165,7 +171,7 @@ class NCBIWindowController:
|
|
| 165 |
self.rename_files(self.model.files)
|
| 166 |
elif not self.unavailable_files:
|
| 167 |
show_message(12, QtWidgets.QMessageBox.Icon.Warning, "No Files Downloaded", "No files were downloaded. Please check your selection and try again.")
|
| 168 |
-
|
| 169 |
def show_unavailable_files_warning(self):
|
| 170 |
warning_text = "The following files were not available for download:\n\n"
|
| 171 |
for species_name, strain in self.unavailable_files:
|
|
@@ -209,22 +215,18 @@ class NCBIWindowController:
|
|
| 209 |
|
| 210 |
def on_rename_complete(self):
|
| 211 |
try:
|
| 212 |
-
# self.update_main_window()
|
| 213 |
self.view.set_download_files_status_label("Download and renaming complete.<br>Press Back to go back to the Main window.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
except Exception as e:
|
| 215 |
self.logger.error(f"Error in on_rename_complete: {str(e)}", exc_info=True)
|
| 216 |
show_error(self.settings, "Error after renaming", str(e))
|
| 217 |
|
| 218 |
-
def update_main_window(self):
|
| 219 |
-
try:
|
| 220 |
-
if hasattr(self.main_window, 'fill_annotation_dropdown'):
|
| 221 |
-
self.main_window.fill_annotation_dropdown()
|
| 222 |
-
else:
|
| 223 |
-
self.logger.warning("MainWindowController does not have fill_annotation_dropdown method")
|
| 224 |
-
except Exception as e:
|
| 225 |
-
self.logger.error(f"Error updating main window: {str(e)}", exc_info=True)
|
| 226 |
-
show_error(self.settings, "Error updating main window", str(e))
|
| 227 |
-
|
| 228 |
def select_all_rows_in_table(self):
|
| 229 |
if self.view.check_box_select_all_rows.isChecked():
|
| 230 |
self.view.table_ncbi_results.selectAll()
|
|
@@ -239,105 +241,3 @@ class NCBIWindowController:
|
|
| 239 |
show_message("Warning!",
|
| 240 |
"The GenBank collection may contain poorly or partially annotated annotation files. "
|
| 241 |
"We highly recommend using the RefSeq collection if it is available.")
|
| 242 |
-
|
| 243 |
-
class DownloadThread(QtCore.QThread):
|
| 244 |
-
finished = QtCore.pyqtSignal(bool)
|
| 245 |
-
progress_updated = QtCore.pyqtSignal(int, int, int)
|
| 246 |
-
status_updated = QtCore.pyqtSignal(str)
|
| 247 |
-
all_completed = QtCore.pyqtSignal() # New signal to indicate all operations are complete
|
| 248 |
-
|
| 249 |
-
def __init__(self, controller, url, id, species_name, strain, download_fna, download_gbff):
|
| 250 |
-
super().__init__()
|
| 251 |
-
self.controller = controller
|
| 252 |
-
self.url = url
|
| 253 |
-
self.id = id
|
| 254 |
-
self.species_name = species_name
|
| 255 |
-
self.strain = strain
|
| 256 |
-
self.download_fna = download_fna
|
| 257 |
-
self.download_gbff = download_gbff
|
| 258 |
-
|
| 259 |
-
def run(self):
|
| 260 |
-
try:
|
| 261 |
-
parsed_url = urlparse(self.url)
|
| 262 |
-
ftp_host = parsed_url.netloc
|
| 263 |
-
ftp_path = parsed_url.path
|
| 264 |
-
|
| 265 |
-
self.controller.logger.info(f"Attempting to connect to FTP server: {ftp_host}")
|
| 266 |
-
|
| 267 |
-
# Resolve the IP address
|
| 268 |
-
try:
|
| 269 |
-
ip_address = socket.gethostbyname(ftp_host)
|
| 270 |
-
self.controller.logger.info(f"Resolved IP address: {ip_address}")
|
| 271 |
-
except socket.gaierror as e:
|
| 272 |
-
self.controller.logger.error(f"Failed to resolve hostname: {ftp_host}. Error: {str(e)}")
|
| 273 |
-
self.finished.emit(False)
|
| 274 |
-
return
|
| 275 |
-
|
| 276 |
-
ftp = FTP(ftp_host)
|
| 277 |
-
ftp.login()
|
| 278 |
-
ftp.cwd(ftp_path)
|
| 279 |
-
ftp.set_pasv(True) # Use passive mode
|
| 280 |
-
ftp.voidcmd('TYPE I') # Set binary mode
|
| 281 |
-
|
| 282 |
-
self.controller.logger.info(f"Successfully connected to FTP server: {ftp_host}")
|
| 283 |
-
|
| 284 |
-
files_to_download = []
|
| 285 |
-
if self.download_fna:
|
| 286 |
-
fna_files = [f for f in ftp.nlst() if f.endswith('_genomic.fna.gz')]
|
| 287 |
-
self.controller.logger.info(f"Found {len(fna_files)} FNA files")
|
| 288 |
-
files_to_download.extend(fna_files)
|
| 289 |
-
if self.download_gbff:
|
| 290 |
-
gbff_files = [f for f in ftp.nlst() if f.endswith('_genomic.gbff.gz')]
|
| 291 |
-
self.controller.logger.info(f"Found {len(gbff_files)} GBFF files")
|
| 292 |
-
files_to_download.extend(gbff_files)
|
| 293 |
-
|
| 294 |
-
self.controller.logger.info(f"Total files to download: {len(files_to_download)}")
|
| 295 |
-
|
| 296 |
-
total_size = 0
|
| 297 |
-
for file in files_to_download:
|
| 298 |
-
try:
|
| 299 |
-
total_size += ftp.size(file)
|
| 300 |
-
except Exception as e:
|
| 301 |
-
self.controller.logger.warning(f"Could not get size for file {file}: {str(e)}")
|
| 302 |
-
|
| 303 |
-
downloaded_size = 0
|
| 304 |
-
|
| 305 |
-
for file in files_to_download:
|
| 306 |
-
self.status_updated.emit(f"Downloading: {file}")
|
| 307 |
-
file_type = 'FNA' if file.endswith('.fna.gz') else 'GBFF'
|
| 308 |
-
output_dir = os.path.join(self.controller.settings.CSPR_DB, file_type)
|
| 309 |
-
|
| 310 |
-
# Create the output directory if it doesn't exist
|
| 311 |
-
os.makedirs(output_dir, exist_ok=True)
|
| 312 |
-
|
| 313 |
-
local_filename = os.path.join(output_dir, file)
|
| 314 |
-
|
| 315 |
-
self.controller.logger.info(f"Downloading file: {file} to {local_filename}")
|
| 316 |
-
|
| 317 |
-
with open(local_filename, 'wb') as local_file:
|
| 318 |
-
def callback(data):
|
| 319 |
-
local_file.write(data)
|
| 320 |
-
nonlocal downloaded_size
|
| 321 |
-
downloaded_size += len(data)
|
| 322 |
-
if total_size > 0:
|
| 323 |
-
self.progress_updated.emit(self.id, downloaded_size, total_size)
|
| 324 |
-
|
| 325 |
-
ftp.retrbinary(f"RETR {file}", callback)
|
| 326 |
-
|
| 327 |
-
self.controller.logger.info(f"Download complete: {file}")
|
| 328 |
-
self.status_updated.emit(f"Decompressing: {file}")
|
| 329 |
-
|
| 330 |
-
# Decompress the file
|
| 331 |
-
self.controller.model.decompress_file(local_filename)
|
| 332 |
-
|
| 333 |
-
# Add the decompressed file to the model's files list
|
| 334 |
-
decompressed_filename = local_filename[:-3] # Remove .gz extension
|
| 335 |
-
self.controller.model.add_downloaded_file(decompressed_filename)
|
| 336 |
-
|
| 337 |
-
ftp.quit()
|
| 338 |
-
self.controller.logger.info(f"All files downloaded and decompressed successfully for ID: {self.id}")
|
| 339 |
-
self.all_completed.emit() # Emit the new signal when everything is done
|
| 340 |
-
self.finished.emit(True)
|
| 341 |
-
except Exception as e:
|
| 342 |
-
self.controller.logger.error(f"Download error for ID {self.id}: {str(e)}", exc_info=True)
|
| 343 |
-
self.finished.emit(False)
|
|
|
|
| 18 |
self.model = NCBIWindowModel(settings)
|
| 19 |
self.view = NCBIWindowView(settings)
|
| 20 |
|
| 21 |
+
# Connect to the initialization complete signal
|
| 22 |
+
self.view.initialization_complete.connect(self.setup_connections)
|
| 23 |
+
|
| 24 |
self._init_ui()
|
|
|
|
| 25 |
except Exception as e:
|
| 26 |
show_error(self.settings, "Error initializing NCBIWindowController", str(e))
|
| 27 |
|
| 28 |
def setup_connections(self):
|
| 29 |
+
"""Set up connections after UI is fully initialized"""
|
| 30 |
try:
|
| 31 |
self.view.push_button_search.clicked.connect(self.search_ncbi)
|
| 32 |
self.view.push_button_download_files.clicked.connect(self.download_files_wrapper)
|
| 33 |
self.view.check_box_select_all_rows.clicked.connect(self.select_all_rows_in_table)
|
| 34 |
self.view.radio_button_collections_genbank.toggled.connect(self.is_checked_GenBank_radio_button)
|
| 35 |
+
|
| 36 |
+
self.logger.debug("NCBI Window connections setup completed")
|
| 37 |
except Exception as e:
|
| 38 |
self.logger.error(f"Error setting up connections: {str(e)}", exc_info=True)
|
| 39 |
show_error(self.settings, "Error setting up connections", str(e))
|
|
|
|
| 139 |
self.on_thread_completed() # Increment completed threads for unavailable files
|
| 140 |
continue
|
| 141 |
|
| 142 |
+
downloader = self.model.DownloadThread(self, url, id, species_name, strain,
|
| 143 |
+
self.view.check_box_file_types_fna.isChecked(),
|
| 144 |
+
self.view.check_box_file_types_gbff.isChecked())
|
| 145 |
downloader.finished.connect(self.on_download_finished)
|
| 146 |
downloader.progress_updated.connect(self.update_progress)
|
| 147 |
downloader.status_updated.connect(self.update_status)
|
|
|
|
| 171 |
self.rename_files(self.model.files)
|
| 172 |
elif not self.unavailable_files:
|
| 173 |
show_message(12, QtWidgets.QMessageBox.Icon.Warning, "No Files Downloaded", "No files were downloaded. Please check your selection and try again.")
|
| 174 |
+
|
| 175 |
def show_unavailable_files_warning(self):
|
| 176 |
warning_text = "The following files were not available for download:\n\n"
|
| 177 |
for species_name, strain in self.unavailable_files:
|
|
|
|
| 215 |
|
| 216 |
def on_rename_complete(self):
|
| 217 |
try:
|
|
|
|
| 218 |
self.view.set_download_files_status_label("Download and renaming complete.<br>Press Back to go back to the Main window.")
|
| 219 |
+
|
| 220 |
+
# Just trigger the database state update
|
| 221 |
+
self.settings.update_db_state()
|
| 222 |
+
|
| 223 |
+
# No need to manually refresh the Home tab as it will receive the db_state_updated signal
|
| 224 |
+
self.logger.info("Database state update triggered after NCBI download")
|
| 225 |
+
|
| 226 |
except Exception as e:
|
| 227 |
self.logger.error(f"Error in on_rename_complete: {str(e)}", exc_info=True)
|
| 228 |
show_error(self.settings, "Error after renaming", str(e))
|
| 229 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
def select_all_rows_in_table(self):
|
| 231 |
if self.view.check_box_select_all_rows.isChecked():
|
| 232 |
self.view.table_ncbi_results.selectAll()
|
|
|
|
| 241 |
show_message("Warning!",
|
| 242 |
"The GenBank collection may contain poorly or partially annotated annotation files. "
|
| 243 |
"We highly recommend using the RefSeq collection if it is available.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -25,7 +25,6 @@ class NewEndonucleaseController:
|
|
| 25 |
self.view.push_button_reset_form.clicked.connect(self._handle_reset_form)
|
| 26 |
self.view.push_button_delete.clicked.connect(self._handle_delete)
|
| 27 |
self.view.combo_box_select_endonuclease.currentIndexChanged.connect(self._handle_endonuclease_selection)
|
| 28 |
-
self.view.show()
|
| 29 |
except Exception as e:
|
| 30 |
show_error(self.settings, "Error setting up connections in NewEndonucleaseController", str(e))
|
| 31 |
|
|
|
|
| 25 |
self.view.push_button_reset_form.clicked.connect(self._handle_reset_form)
|
| 26 |
self.view.push_button_delete.clicked.connect(self._handle_delete)
|
| 27 |
self.view.combo_box_select_endonuclease.currentIndexChanged.connect(self._handle_endonuclease_selection)
|
|
|
|
| 28 |
except Exception as e:
|
| 29 |
show_error(self.settings, "Error setting up connections in NewEndonucleaseController", str(e))
|
| 30 |
|
|
File without changes
|
|
@@ -2,58 +2,85 @@ from PyQt6 import QtWidgets
|
|
| 2 |
from utils.ui import show_error, show_message, position_window
|
| 3 |
from views.PopulationAnalysisWindowView import PopulationAnalysisWindowView
|
| 4 |
from models.PopulationAnalysisWindowModel import PopulationAnalysisWindowModel
|
|
|
|
| 5 |
|
| 6 |
class PopulationAnalysisWindowController:
|
| 7 |
def __init__(self, global_settings):
|
| 8 |
self.global_settings = global_settings
|
| 9 |
-
self.model = PopulationAnalysisWindowModel(global_settings)
|
| 10 |
-
self.view = PopulationAnalysisWindowView(global_settings)
|
|
|
|
| 11 |
self.setup_connections()
|
| 12 |
|
| 13 |
def setup_connections(self):
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
def launch(self):
|
| 26 |
try:
|
|
|
|
|
|
|
| 27 |
self.get_data()
|
| 28 |
-
self.view.show()
|
| 29 |
except Exception as e:
|
|
|
|
| 30 |
show_error(self.global_settings, "Error in launch() in population analysis.", str(e))
|
| 31 |
|
| 32 |
def get_data(self):
|
| 33 |
try:
|
|
|
|
| 34 |
self.fillEndo()
|
| 35 |
except Exception as e:
|
|
|
|
| 36 |
show_error(self.global_settings, "Error in get_data() in population analysis.", str(e))
|
| 37 |
|
| 38 |
def fillEndo(self):
|
| 39 |
try:
|
|
|
|
| 40 |
endos = self.model.load_endonucleases()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
self.view.update_endo_dropdown(endos.keys())
|
| 42 |
self.change_endo()
|
| 43 |
except Exception as e:
|
|
|
|
| 44 |
show_error(self.global_settings, "Error in fillEndo() in population analysis.", str(e))
|
| 45 |
|
| 46 |
def change_endo(self):
|
| 47 |
try:
|
| 48 |
selected_endo = self.view.get_selected_endo()
|
|
|
|
|
|
|
| 49 |
org_files = self.model.get_organism_files(selected_endo)
|
| 50 |
self.view.update_org_table(org_files)
|
| 51 |
except Exception as e:
|
| 52 |
-
show_error(self.
|
| 53 |
|
| 54 |
def pre_analyze(self):
|
| 55 |
try:
|
| 56 |
-
selected_indexes = self.view.
|
| 57 |
if len(selected_indexes) == 0:
|
| 58 |
show_message(
|
| 59 |
fontSize=12,
|
|
@@ -73,37 +100,104 @@ class PopulationAnalysisWindowController:
|
|
| 73 |
|
| 74 |
def fill_data(self):
|
| 75 |
try:
|
| 76 |
-
self.view.show_loading_window(5)
|
| 77 |
self.model.seeds = self.model.get_shared_seeds(self.model.db_files, True)
|
| 78 |
|
| 79 |
if len(self.model.seeds) == 0:
|
| 80 |
-
self.view.hide_loading_window()
|
| 81 |
return
|
| 82 |
|
| 83 |
seed_data = []
|
| 84 |
for seed in self.model.seeds:
|
| 85 |
data = self.model.get_seed_data(seed, self.model.db_files)
|
| 86 |
-
|
|
|
|
|
|
|
| 87 |
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
-
self.view.hide_loading_window()
|
| 95 |
except Exception as e:
|
| 96 |
show_error(self.global_settings, "Error in fill_data() in population analysis.", str(e))
|
| 97 |
|
| 98 |
def process_seed_data(self, seed, data):
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
def custom_seed_search(self):
|
| 105 |
try:
|
| 106 |
-
seeds = self.view.
|
| 107 |
seeds = [seed.strip().upper() for seed in seeds if seed.strip()]
|
| 108 |
|
| 109 |
if not seeds:
|
|
@@ -124,7 +218,7 @@ class PopulationAnalysisWindowController:
|
|
| 124 |
)
|
| 125 |
return
|
| 126 |
|
| 127 |
-
self.view.
|
| 128 |
except Exception as e:
|
| 129 |
show_error(self.global_settings, "Error in custom_seed_search() in population analysis.", str(e))
|
| 130 |
|
|
@@ -140,6 +234,10 @@ class PopulationAnalysisWindowController:
|
|
| 140 |
)
|
| 141 |
return
|
| 142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
locations = self.model.get_seed_locations(selected_seeds, self.model.db_files)
|
| 144 |
self.view.update_loc_finder_table(locations)
|
| 145 |
except Exception as e:
|
|
@@ -151,11 +249,11 @@ class PopulationAnalysisWindowController:
|
|
| 151 |
except Exception as e:
|
| 152 |
show_error(self.global_settings, "Error in clear_loc_table() in population analysis.", str(e))
|
| 153 |
|
| 154 |
-
def
|
| 155 |
try:
|
| 156 |
self.view.sort_table2(logicalIndex)
|
| 157 |
except Exception as e:
|
| 158 |
-
show_error(self.global_settings, "Error in
|
| 159 |
|
| 160 |
def loc_table_sorter(self, logicalIndex):
|
| 161 |
try:
|
|
|
|
| 2 |
from utils.ui import show_error, show_message, position_window
|
| 3 |
from views.PopulationAnalysisWindowView import PopulationAnalysisWindowView
|
| 4 |
from models.PopulationAnalysisWindowModel import PopulationAnalysisWindowModel
|
| 5 |
+
import logging
|
| 6 |
|
| 7 |
class PopulationAnalysisWindowController:
|
| 8 |
def __init__(self, global_settings):
|
| 9 |
self.global_settings = global_settings
|
| 10 |
+
self.model = PopulationAnalysisWindowModel(self.global_settings)
|
| 11 |
+
self.view = PopulationAnalysisWindowView(self.global_settings)
|
| 12 |
+
self._init_ui()
|
| 13 |
self.setup_connections()
|
| 14 |
|
| 15 |
def setup_connections(self):
|
| 16 |
+
try:
|
| 17 |
+
# Group: Select Organisms
|
| 18 |
+
self.view.combo_box_endonuclease.currentIndexChanged.connect(self.change_endo)
|
| 19 |
+
self.view.push_button_analyze_organism.clicked.connect(self.pre_analyze)
|
| 20 |
+
|
| 21 |
+
# Group: Seed Analysis
|
| 22 |
+
self.view.push_button_query_seed.clicked.connect(self.custom_seed_search)
|
| 23 |
+
self.view.push_button_clear_seeds.clicked.connect(self.clear)
|
| 24 |
+
self.view.push_button_find_locations.clicked.connect(self.find_locations)
|
| 25 |
+
self.view.push_button_clear_locations.clicked.connect(self.clear_loc_table)
|
| 26 |
+
|
| 27 |
+
# Tables sorting
|
| 28 |
+
self.view.table_seed.horizontalHeader().sectionClicked.connect(self.seed_table_sorting)
|
| 29 |
+
self.view.table_locations.horizontalHeader().sectionClicked.connect(self.loc_table_sorter)
|
| 30 |
+
except Exception as e:
|
| 31 |
+
show_error(self.global_settings, "Error setting up connections in population analysis.", str(e))
|
| 32 |
+
|
| 33 |
+
def _init_ui(self):
|
| 34 |
+
self.launch()
|
| 35 |
|
| 36 |
def launch(self):
|
| 37 |
try:
|
| 38 |
+
self.logger = self.global_settings.get_logger()
|
| 39 |
+
self.logger.info("Launching Population Analysis Window")
|
| 40 |
self.get_data()
|
|
|
|
| 41 |
except Exception as e:
|
| 42 |
+
self.logger.error(f"Error in launch(): {str(e)}")
|
| 43 |
show_error(self.global_settings, "Error in launch() in population analysis.", str(e))
|
| 44 |
|
| 45 |
def get_data(self):
|
| 46 |
try:
|
| 47 |
+
self.logger.info("Getting data for Population Analysis")
|
| 48 |
self.fillEndo()
|
| 49 |
except Exception as e:
|
| 50 |
+
self.logger.error(f"Error in get_data(): {str(e)}")
|
| 51 |
show_error(self.global_settings, "Error in get_data() in population analysis.", str(e))
|
| 52 |
|
| 53 |
def fillEndo(self):
|
| 54 |
try:
|
| 55 |
+
self.logger.info("Starting fillEndo()")
|
| 56 |
endos = self.model.load_endonucleases()
|
| 57 |
+
self.logger.debug(f"Loaded endonucleases: {endos}")
|
| 58 |
+
|
| 59 |
+
if not endos:
|
| 60 |
+
self.logger.warning("No endonucleases found")
|
| 61 |
+
show_error(self.global_settings, "Error", "No endonucleases found")
|
| 62 |
+
return
|
| 63 |
+
|
| 64 |
+
self.logger.info(f"Updating dropdown with {len(endos)} endonucleases")
|
| 65 |
self.view.update_endo_dropdown(endos.keys())
|
| 66 |
self.change_endo()
|
| 67 |
except Exception as e:
|
| 68 |
+
self.logger.error(f"Error in fillEndo(): {str(e)}")
|
| 69 |
show_error(self.global_settings, "Error in fillEndo() in population analysis.", str(e))
|
| 70 |
|
| 71 |
def change_endo(self):
|
| 72 |
try:
|
| 73 |
selected_endo = self.view.get_selected_endo()
|
| 74 |
+
if not selected_endo:
|
| 75 |
+
return
|
| 76 |
org_files = self.model.get_organism_files(selected_endo)
|
| 77 |
self.view.update_org_table(org_files)
|
| 78 |
except Exception as e:
|
| 79 |
+
show_error(self.settings, "Error in change_endo() in population analysis.", str(e))
|
| 80 |
|
| 81 |
def pre_analyze(self):
|
| 82 |
try:
|
| 83 |
+
selected_indexes = [index.row() for index in self.view.table_organism.selectionModel().selectedRows()]
|
| 84 |
if len(selected_indexes) == 0:
|
| 85 |
show_message(
|
| 86 |
fontSize=12,
|
|
|
|
| 100 |
|
| 101 |
def fill_data(self):
|
| 102 |
try:
|
|
|
|
| 103 |
self.model.seeds = self.model.get_shared_seeds(self.model.db_files, True)
|
| 104 |
|
| 105 |
if len(self.model.seeds) == 0:
|
|
|
|
| 106 |
return
|
| 107 |
|
| 108 |
seed_data = []
|
| 109 |
for seed in self.model.seeds:
|
| 110 |
data = self.model.get_seed_data(seed, self.model.db_files)
|
| 111 |
+
processed_data = self.process_seed_data(seed, data)
|
| 112 |
+
if processed_data: # Only add if data was processed successfully
|
| 113 |
+
seed_data.append(processed_data)
|
| 114 |
|
| 115 |
+
if seed_data: # Only update table if we have data
|
| 116 |
+
self.view.update_shared_seeds_table(seed_data)
|
| 117 |
+
|
| 118 |
+
if len(self.model.db_files) > 1:
|
| 119 |
+
heatmap_data = self.model.get_heatmap_data(self.model.db_files)
|
| 120 |
+
self.view.plot_heatmap(heatmap_data, self.model.org_names)
|
| 121 |
+
else:
|
| 122 |
+
self.logger.warning("No seed data was processed successfully")
|
| 123 |
|
|
|
|
| 124 |
except Exception as e:
|
| 125 |
show_error(self.global_settings, "Error in fill_data() in population analysis.", str(e))
|
| 126 |
|
| 127 |
def process_seed_data(self, seed, data):
|
| 128 |
+
"""Process seed data and return a tuple of values for the table"""
|
| 129 |
+
try:
|
| 130 |
+
# self.logger.debug(f"Processing seed data: {data}")
|
| 131 |
+
|
| 132 |
+
if not data or data['org_count'] == 0:
|
| 133 |
+
self.logger.warning(f"No data found for seed {seed}")
|
| 134 |
+
return None
|
| 135 |
+
|
| 136 |
+
# Calculate coverage percentage
|
| 137 |
+
coverage = (data['org_count'] / len(self.model.db_files)) * 100
|
| 138 |
+
coverage = float("%.2f" % coverage)
|
| 139 |
+
|
| 140 |
+
# Calculate average repeats per scaffold
|
| 141 |
+
avg_rep_per_scaff = data['total_count'] / data['org_count']
|
| 142 |
+
avg_rep_per_scaff = float("%.2f" % avg_rep_per_scaff)
|
| 143 |
+
|
| 144 |
+
# Handle missing data in threes/fives
|
| 145 |
+
threes = data['threes']
|
| 146 |
+
fives = data['fives']
|
| 147 |
+
if len(threes) < len(fives):
|
| 148 |
+
threes.extend([''] * (len(fives) - len(threes)))
|
| 149 |
+
elif len(fives) < len(threes):
|
| 150 |
+
fives.extend([''] * (len(threes) - len(fives)))
|
| 151 |
+
|
| 152 |
+
# Find majority sequence
|
| 153 |
+
majority_index = 0
|
| 154 |
+
if not threes or threes[0] == '':
|
| 155 |
+
majority = max(set(fives), key=fives.count)
|
| 156 |
+
majority_index = fives.index(majority)
|
| 157 |
+
consensus_seq = fives[majority_index] + seed
|
| 158 |
+
percent_consensus = (fives.count(fives[majority_index]) / len(fives)) * 100
|
| 159 |
+
elif not fives or fives[0] == '':
|
| 160 |
+
majority = max(set(threes), key=threes.count)
|
| 161 |
+
majority_index = threes.index(majority)
|
| 162 |
+
consensus_seq = seed + threes[majority_index]
|
| 163 |
+
percent_consensus = (threes.count(threes[majority_index]) / len(threes)) * 100
|
| 164 |
+
else:
|
| 165 |
+
# Both threes and fives present
|
| 166 |
+
combined = [f"{f}{t}" for f, t in zip(fives, threes)]
|
| 167 |
+
majority = max(set(combined), key=combined.count)
|
| 168 |
+
majority_index = combined.index(majority)
|
| 169 |
+
consensus_seq = fives[majority_index] + seed + threes[majority_index]
|
| 170 |
+
percent_consensus = (combined.count(majority) / len(combined)) * 100
|
| 171 |
+
|
| 172 |
+
percent_consensus = float("%.2f" % percent_consensus)
|
| 173 |
+
|
| 174 |
+
# Determine strand
|
| 175 |
+
strand = "+" if int(data['locs'][majority_index]) >= 0 else "-"
|
| 176 |
+
|
| 177 |
+
# Create the row data
|
| 178 |
+
row_data = (
|
| 179 |
+
seed, # Seed
|
| 180 |
+
coverage, # % Coverage
|
| 181 |
+
data['total_count'], # Total Repeats
|
| 182 |
+
avg_rep_per_scaff, # Avg. Repeats/Scaffold
|
| 183 |
+
consensus_seq, # Consensus Sequence
|
| 184 |
+
percent_consensus, # % Consensus
|
| 185 |
+
data['scores'][majority_index], # Score
|
| 186 |
+
data['pams'][majority_index], # PAM
|
| 187 |
+
strand # Strand
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
self.logger.debug(f"Processed seed data: {row_data}")
|
| 191 |
+
return row_data
|
| 192 |
+
|
| 193 |
+
except Exception as e:
|
| 194 |
+
self.logger.error(f"Error processing seed data: {str(e)}")
|
| 195 |
+
show_error(self.global_settings, f"Error processing seed {seed}", str(e))
|
| 196 |
+
return None
|
| 197 |
|
| 198 |
def custom_seed_search(self):
|
| 199 |
try:
|
| 200 |
+
seeds = self.view.line_edit_seed.text().split(',')
|
| 201 |
seeds = [seed.strip().upper() for seed in seeds if seed.strip()]
|
| 202 |
|
| 203 |
if not seeds:
|
|
|
|
| 218 |
)
|
| 219 |
return
|
| 220 |
|
| 221 |
+
self.view.update_seed_table(seed_data)
|
| 222 |
except Exception as e:
|
| 223 |
show_error(self.global_settings, "Error in custom_seed_search() in population analysis.", str(e))
|
| 224 |
|
|
|
|
| 234 |
)
|
| 235 |
return
|
| 236 |
|
| 237 |
+
# Clear the locations table before adding new entries
|
| 238 |
+
self.view.table_locations.setRowCount(0)
|
| 239 |
+
|
| 240 |
+
# Get and display new locations
|
| 241 |
locations = self.model.get_seed_locations(selected_seeds, self.model.db_files)
|
| 242 |
self.view.update_loc_finder_table(locations)
|
| 243 |
except Exception as e:
|
|
|
|
| 249 |
except Exception as e:
|
| 250 |
show_error(self.global_settings, "Error in clear_loc_table() in population analysis.", str(e))
|
| 251 |
|
| 252 |
+
def seed_table_sorting(self, logicalIndex):
|
| 253 |
try:
|
| 254 |
self.view.sort_table2(logicalIndex)
|
| 255 |
except Exception as e:
|
| 256 |
+
show_error(self.global_settings, "Error in seed_table_sorting() in population analysis.", str(e))
|
| 257 |
|
| 258 |
def loc_table_sorter(self, logicalIndex):
|
| 259 |
try:
|
|
@@ -1,13 +1,21 @@
|
|
|
|
|
| 1 |
from models.ViewTargetsModel import ViewTargetsModel
|
| 2 |
from views.ViewTargetsView import ViewTargetsView
|
| 3 |
from PyQt6.QtWidgets import QMessageBox
|
| 4 |
from utils.ui import show_error
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
class ViewTargetsController:
|
| 7 |
def __init__(self, global_settings):
|
| 8 |
self.global_settings = global_settings
|
|
|
|
| 9 |
self.model = ViewTargetsModel(global_settings)
|
| 10 |
self.view = ViewTargetsView(global_settings)
|
|
|
|
|
|
|
|
|
|
| 11 |
self.setup_connections()
|
| 12 |
self.organism = ""
|
| 13 |
self.endonuclease = ""
|
|
@@ -26,28 +34,86 @@ class ViewTargetsController:
|
|
| 26 |
|
| 27 |
def load_targets(self, selected_targets, organism, endonuclease):
|
| 28 |
try:
|
|
|
|
|
|
|
| 29 |
self.organism = organism
|
| 30 |
self.endonuclease = endonuclease
|
|
|
|
|
|
|
|
|
|
| 31 |
self.model.load_targets(selected_targets, organism, endonuclease)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
targets = self.model.get_targets()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
self.view.display_targets_in_table(targets)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
self.view.set_combo_box_endonuclease([endonuclease])
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
except Exception as e:
|
| 37 |
show_error(self.global_settings, "Error loading targets", str(e))
|
| 38 |
|
| 39 |
-
def load_gene_viewer(self
|
| 40 |
try:
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
| 43 |
if genes:
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
self.view.
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
except Exception as e:
|
| 50 |
-
|
| 51 |
|
| 52 |
def perform_off_target_analysis(self):
|
| 53 |
try:
|
|
@@ -77,13 +143,61 @@ class ViewTargetsController:
|
|
| 77 |
|
| 78 |
def highlight_gene_viewer(self):
|
| 79 |
try:
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
return
|
| 84 |
-
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
except Exception as e:
|
|
|
|
| 87 |
show_error(self.global_settings, "Error highlighting gene viewer", str(e))
|
| 88 |
|
| 89 |
def export_targets(self):
|
|
|
|
| 1 |
+
import logging
|
| 2 |
from models.ViewTargetsModel import ViewTargetsModel
|
| 3 |
from views.ViewTargetsView import ViewTargetsView
|
| 4 |
from PyQt6.QtWidgets import QMessageBox
|
| 5 |
from utils.ui import show_error
|
| 6 |
+
import time
|
| 7 |
+
import traceback
|
| 8 |
+
import threading
|
| 9 |
|
| 10 |
class ViewTargetsController:
|
| 11 |
def __init__(self, global_settings):
|
| 12 |
self.global_settings = global_settings
|
| 13 |
+
start_time = time.time()
|
| 14 |
self.model = ViewTargetsModel(global_settings)
|
| 15 |
self.view = ViewTargetsView(global_settings)
|
| 16 |
+
init_time = time.time() - start_time
|
| 17 |
+
self.global_settings.logger.debug(f"ViewTargets initialization took: {init_time:.2f} seconds")
|
| 18 |
+
|
| 19 |
self.setup_connections()
|
| 20 |
self.organism = ""
|
| 21 |
self.endonuclease = ""
|
|
|
|
| 34 |
|
| 35 |
def load_targets(self, selected_targets, organism, endonuclease):
|
| 36 |
try:
|
| 37 |
+
total_start = time.time()
|
| 38 |
+
|
| 39 |
self.organism = organism
|
| 40 |
self.endonuclease = endonuclease
|
| 41 |
+
|
| 42 |
+
# Time model loading
|
| 43 |
+
model_start = time.time()
|
| 44 |
self.model.load_targets(selected_targets, organism, endonuclease)
|
| 45 |
+
model_time = time.time() - model_start
|
| 46 |
+
self.global_settings.logger.debug(f"Model load_targets took: {model_time:.2f} seconds")
|
| 47 |
+
|
| 48 |
+
# Time getting targets
|
| 49 |
+
targets_start = time.time()
|
| 50 |
targets = self.model.get_targets()
|
| 51 |
+
targets_time = time.time() - targets_start
|
| 52 |
+
self.global_settings.logger.debug(f"Getting targets took: {targets_time:.2f} seconds")
|
| 53 |
+
|
| 54 |
+
# Time displaying targets
|
| 55 |
+
display_start = time.time()
|
| 56 |
self.view.display_targets_in_table(targets)
|
| 57 |
+
display_time = time.time() - display_start
|
| 58 |
+
self.global_settings.logger.debug(f"Displaying targets took: {display_time:.2f} seconds")
|
| 59 |
+
|
| 60 |
+
# Time setting endonuclease
|
| 61 |
+
endo_start = time.time()
|
| 62 |
self.view.set_combo_box_endonuclease([endonuclease])
|
| 63 |
+
endo_time = time.time() - endo_start
|
| 64 |
+
self.global_settings.logger.debug(f"Setting endonuclease took: {endo_time:.2f} seconds")
|
| 65 |
+
|
| 66 |
+
# Time loading gene viewer
|
| 67 |
+
gene_start = time.time()
|
| 68 |
+
self.load_gene_viewer()
|
| 69 |
+
gene_time = time.time() - gene_start
|
| 70 |
+
self.global_settings.logger.debug(f"Loading gene viewer took: {gene_time:.2f} seconds")
|
| 71 |
+
|
| 72 |
+
total_time = time.time() - total_start
|
| 73 |
+
self.global_settings.logger.debug(f"Total load_targets took: {total_time:.2f} seconds")
|
| 74 |
+
|
| 75 |
except Exception as e:
|
| 76 |
show_error(self.global_settings, "Error loading targets", str(e))
|
| 77 |
|
| 78 |
+
def load_gene_viewer(self):
|
| 79 |
try:
|
| 80 |
+
start_time = time.time()
|
| 81 |
+
|
| 82 |
+
# Get available genes from the model
|
| 83 |
+
genes = self.model.get_available_genes()
|
| 84 |
if genes:
|
| 85 |
+
# Update the gene combo box
|
| 86 |
+
self.view.combo_box_gene.clear()
|
| 87 |
+
self.view.combo_box_gene.addItems(genes)
|
| 88 |
+
|
| 89 |
+
# Fetch first gene immediately
|
| 90 |
+
first_gene = genes[0]
|
| 91 |
+
gene_data = self.model.get_gene_data(first_gene)
|
| 92 |
+
|
| 93 |
+
if gene_data:
|
| 94 |
+
# Update the gene viewer with sequence
|
| 95 |
+
self.view.set_text_edit_gene_viewer(gene_data['sequence'])
|
| 96 |
+
|
| 97 |
+
# Update location fields if available
|
| 98 |
+
if 'info' in gene_data and 'feature_location' in gene_data['info']:
|
| 99 |
+
location = gene_data['info']['feature_location']
|
| 100 |
+
if ':' in location:
|
| 101 |
+
start, end = location.split(':')[0], location.split(':')[1].split('(')[0]
|
| 102 |
+
self.view.line_edit_start_location.setText(start)
|
| 103 |
+
self.view.line_edit_stop_location.setText(end)
|
| 104 |
+
|
| 105 |
+
# Pre-fetch next few genes in background thread
|
| 106 |
+
def prefetch_genes():
|
| 107 |
+
for gene in genes[1:5]: # Pre-fetch next 4 genes
|
| 108 |
+
self.model.get_gene_data(gene)
|
| 109 |
+
|
| 110 |
+
threading.Thread(target=prefetch_genes, daemon=True).start()
|
| 111 |
+
|
| 112 |
+
execution_time = time.time() - start_time
|
| 113 |
+
self.global_settings.logger.debug(f"Loading gene viewer took: {execution_time:.2f} seconds")
|
| 114 |
+
|
| 115 |
except Exception as e:
|
| 116 |
+
self.global_settings.logger.error(f"Error in load_gene_viewer: {str(e)}\n{traceback.format_exc()}")
|
| 117 |
|
| 118 |
def perform_off_target_analysis(self):
|
| 119 |
try:
|
|
|
|
| 143 |
|
| 144 |
def highlight_gene_viewer(self):
|
| 145 |
try:
|
| 146 |
+
self.global_settings.logger.debug("Starting highlight_gene_viewer")
|
| 147 |
+
|
| 148 |
+
# Get selected targets
|
| 149 |
+
selected_rows = self.view.get_selected_targets()
|
| 150 |
+
self.global_settings.logger.debug(f"Selected targets: {selected_rows}")
|
| 151 |
+
|
| 152 |
+
if not selected_rows:
|
| 153 |
+
QMessageBox.warning(self.view, "No Selection",
|
| 154 |
+
"Please select targets to highlight in the gene viewer.")
|
| 155 |
return
|
| 156 |
+
|
| 157 |
+
# Convert table selections to the format expected by the model
|
| 158 |
+
targets_to_highlight = []
|
| 159 |
+
for target in selected_rows:
|
| 160 |
+
target_info = {
|
| 161 |
+
'location': target['location'],
|
| 162 |
+
'sequence': target['sequence'],
|
| 163 |
+
'strand': target['strand']
|
| 164 |
+
}
|
| 165 |
+
targets_to_highlight.append(target_info)
|
| 166 |
+
self.global_settings.logger.debug(f"Target to highlight: {target_info}")
|
| 167 |
+
|
| 168 |
+
# Get current gene sequence
|
| 169 |
+
current_gene = self.view.combo_box_gene.currentText()
|
| 170 |
+
self.global_settings.logger.debug(f"Current gene: {current_gene}")
|
| 171 |
+
|
| 172 |
+
gene_data = self.model.get_gene_data(current_gene)
|
| 173 |
+
if not gene_data:
|
| 174 |
+
self.global_settings.logger.error("No gene data found")
|
| 175 |
+
QMessageBox.warning(self.view, "No Gene Data",
|
| 176 |
+
"Could not get gene data for highlighting.")
|
| 177 |
+
return
|
| 178 |
+
|
| 179 |
+
self.global_settings.logger.debug(f"Gene sequence length: {len(gene_data['sequence'])}")
|
| 180 |
+
|
| 181 |
+
# Highlight the sequences
|
| 182 |
+
if targets_to_highlight:
|
| 183 |
+
self.global_settings.logger.debug("Attempting to highlight sequences")
|
| 184 |
+
highlighted_sequence = self.model.highlight_targets_in_gene_viewer(targets_to_highlight)
|
| 185 |
+
|
| 186 |
+
if highlighted_sequence:
|
| 187 |
+
self.global_settings.logger.debug("Successfully highlighted sequences")
|
| 188 |
+
self.global_settings.logger.debug(f"Highlighted sequence length: {len(highlighted_sequence)}")
|
| 189 |
+
self.view.update_gene_viewer(highlighted_sequence)
|
| 190 |
+
else:
|
| 191 |
+
self.global_settings.logger.error("Failed to highlight sequences - returned None")
|
| 192 |
+
QMessageBox.warning(self.view, "Highlighting Failed",
|
| 193 |
+
"Could not highlight the selected sequences. They may not be found in the current gene view.")
|
| 194 |
+
else:
|
| 195 |
+
self.global_settings.logger.error("No valid targets to highlight")
|
| 196 |
+
QMessageBox.warning(self.view, "No Valid Targets",
|
| 197 |
+
"Could not get sequence information from the selected rows.")
|
| 198 |
+
|
| 199 |
except Exception as e:
|
| 200 |
+
self.global_settings.logger.error(f"Error in highlight_gene_viewer: {str(e)}\n{traceback.format_exc()}")
|
| 201 |
show_error(self.global_settings, "Error highlighting gene viewer", str(e))
|
| 202 |
|
| 203 |
def export_targets(self):
|
|
@@ -2,6 +2,7 @@ from PyQt6.QtWidgets import QMessageBox
|
|
| 2 |
from Bio import SeqIO
|
| 3 |
import os
|
| 4 |
import traceback
|
|
|
|
| 5 |
|
| 6 |
class AnnotationParser:
|
| 7 |
def __init__(self, global_settings):
|
|
@@ -9,25 +10,34 @@ class AnnotationParser:
|
|
| 9 |
self.logger = global_settings.get_logger()
|
| 10 |
self.annotation_file_name = ""
|
| 11 |
self.available_genes = []
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
def set_annotation_file(self, file_path):
|
| 14 |
-
self.annotation_file_name
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
self.
|
| 25 |
-
self.
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
def
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
def genbank_search(self, queries):
|
| 33 |
try:
|
|
@@ -37,15 +47,34 @@ class AnnotationParser:
|
|
| 37 |
self.logger.debug(f"Searching in annotation file: {self.annotation_file_name}")
|
| 38 |
results_list = []
|
| 39 |
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
for feature in record.features:
|
| 42 |
if feature.type in ['CDS', 'gene']:
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
self.logger.debug(f"Found {len(results_list)} results")
|
| 51 |
return results_list
|
|
@@ -53,6 +82,20 @@ class AnnotationParser:
|
|
| 53 |
self.logger.error(f"Error in genbank_search: {str(e)}")
|
| 54 |
raise
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
def find_which_file_version(self):
|
| 57 |
try:
|
| 58 |
if not self.annotation_file_name or os.path.basename(self.annotation_file_name) == "None":
|
|
@@ -110,25 +153,76 @@ class AnnotationParser:
|
|
| 110 |
def get_available_genes(self):
|
| 111 |
return self.available_genes
|
| 112 |
|
| 113 |
-
def get_gene_data(self,
|
|
|
|
|
|
|
|
|
|
| 114 |
try:
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
}
|
| 126 |
-
|
|
|
|
|
|
|
|
|
|
| 127 |
return None
|
|
|
|
| 128 |
except Exception as e:
|
| 129 |
self.logger.error(f"Error in get_gene_data: {str(e)}")
|
| 130 |
return None
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
def _parse_available_genes(self):
|
| 133 |
self.available_genes = []
|
| 134 |
try:
|
|
|
|
| 2 |
from Bio import SeqIO
|
| 3 |
import os
|
| 4 |
import traceback
|
| 5 |
+
from functools import lru_cache
|
| 6 |
|
| 7 |
class AnnotationParser:
|
| 8 |
def __init__(self, global_settings):
|
|
|
|
| 10 |
self.logger = global_settings.get_logger()
|
| 11 |
self.annotation_file_name = ""
|
| 12 |
self.available_genes = []
|
| 13 |
+
self._feature_cache = {} # Cache for feature data
|
| 14 |
+
self._record_cache = {} # Cache for SeqIO records
|
| 15 |
+
self.gene_cache = {} # Add cache for gene data
|
| 16 |
|
| 17 |
def set_annotation_file(self, file_path):
|
| 18 |
+
if self.annotation_file_name != file_path:
|
| 19 |
+
self.annotation_file_name = file_path
|
| 20 |
+
self.logger.debug(f"Set annotation file to: {file_path}")
|
| 21 |
+
self._feature_cache.clear() # Clear cache when file changes
|
| 22 |
+
self._record_cache.clear()
|
| 23 |
+
if hasattr(self, '_gene_index'):
|
| 24 |
+
delattr(self, '_gene_index')
|
| 25 |
+
|
| 26 |
+
# Pre-load records and build index
|
| 27 |
+
records = self._get_records()
|
| 28 |
+
self._build_gene_index(records)
|
| 29 |
+
self._parse_available_genes()
|
| 30 |
+
|
| 31 |
+
@lru_cache(maxsize=1)
|
| 32 |
+
def _get_records(self):
|
| 33 |
+
"""Cache and return all records from the annotation file"""
|
| 34 |
+
if not self._record_cache:
|
| 35 |
+
try:
|
| 36 |
+
self._record_cache = list(SeqIO.parse(self.annotation_file_name, "genbank"))
|
| 37 |
+
except Exception as e:
|
| 38 |
+
self.logger.error(f"Error reading annotation file: {str(e)}")
|
| 39 |
+
return []
|
| 40 |
+
return self._record_cache
|
| 41 |
|
| 42 |
def genbank_search(self, queries):
|
| 43 |
try:
|
|
|
|
| 47 |
self.logger.debug(f"Searching in annotation file: {self.annotation_file_name}")
|
| 48 |
results_list = []
|
| 49 |
|
| 50 |
+
# Convert queries to lowercase set for faster lookup
|
| 51 |
+
queries = {q.lower() for q in queries}
|
| 52 |
+
|
| 53 |
+
# Use cached records
|
| 54 |
+
for record in self._get_records():
|
| 55 |
for feature in record.features:
|
| 56 |
if feature.type in ['CDS', 'gene']:
|
| 57 |
+
# Create a hashable cache key using feature start and end positions
|
| 58 |
+
cache_key = (record.id, feature.type,
|
| 59 |
+
str(feature.location.start),
|
| 60 |
+
str(feature.location.end))
|
| 61 |
+
|
| 62 |
+
# Use cached feature info if available
|
| 63 |
+
if cache_key not in self._feature_cache:
|
| 64 |
+
self._feature_cache[cache_key] = self._get_feature_info(feature)
|
| 65 |
+
|
| 66 |
+
feature_info = self._feature_cache[cache_key]
|
| 67 |
+
|
| 68 |
+
# Combine searchable text for single comparison
|
| 69 |
+
searchable_text = ' '.join([
|
| 70 |
+
feature_info['feature_name'].lower(),
|
| 71 |
+
feature_info['feature_id'].lower(),
|
| 72 |
+
feature_info['feature_description'].lower()
|
| 73 |
+
])
|
| 74 |
+
|
| 75 |
+
# Check if any query matches
|
| 76 |
+
if any(query in searchable_text for query in queries):
|
| 77 |
+
results_list.append((record.id, feature))
|
| 78 |
|
| 79 |
self.logger.debug(f"Found {len(results_list)} results")
|
| 80 |
return results_list
|
|
|
|
| 82 |
self.logger.error(f"Error in genbank_search: {str(e)}")
|
| 83 |
raise
|
| 84 |
|
| 85 |
+
def get_max_chrom(self):
|
| 86 |
+
try:
|
| 87 |
+
parser = SeqIO.parse(self.annotation_file_name, 'genbank')
|
| 88 |
+
max_chrom = sum(1 for _ in parser)
|
| 89 |
+
return max_chrom
|
| 90 |
+
except Exception as e:
|
| 91 |
+
self.logger.error(f"Error in get_max_chrom: {str(e)}")
|
| 92 |
+
self._show_error("Error in get_max_chrom", str(e))
|
| 93 |
+
return 0
|
| 94 |
+
|
| 95 |
+
def get_sequence_info(self, query):
|
| 96 |
+
# Implement this method if needed
|
| 97 |
+
pass
|
| 98 |
+
|
| 99 |
def find_which_file_version(self):
|
| 100 |
try:
|
| 101 |
if not self.annotation_file_name or os.path.basename(self.annotation_file_name) == "None":
|
|
|
|
| 153 |
def get_available_genes(self):
|
| 154 |
return self.available_genes
|
| 155 |
|
| 156 |
+
def get_gene_data(self, gene_identifier):
|
| 157 |
+
"""
|
| 158 |
+
Get gene data using gene name or locus tag with optimized caching
|
| 159 |
+
"""
|
| 160 |
try:
|
| 161 |
+
self.logger.debug(f"AnnotationParser.get_gene_data called with identifier: {gene_identifier}")
|
| 162 |
+
|
| 163 |
+
if not gene_identifier:
|
| 164 |
+
self.logger.warning("Empty gene identifier provided")
|
| 165 |
+
return None
|
| 166 |
+
|
| 167 |
+
# Handle numeric gene identifiers
|
| 168 |
+
if isinstance(gene_identifier, int) or str(gene_identifier).isdigit():
|
| 169 |
+
if self.available_genes:
|
| 170 |
+
gene_identifier = self.available_genes[0]
|
| 171 |
+
else:
|
| 172 |
+
return None
|
| 173 |
+
|
| 174 |
+
# Check main cache first
|
| 175 |
+
cache_key = f"gene_data_{gene_identifier}"
|
| 176 |
+
if cache_key in self._feature_cache:
|
| 177 |
+
return self._feature_cache[cache_key]
|
| 178 |
+
|
| 179 |
+
# Get cached records
|
| 180 |
+
records = self._get_records()
|
| 181 |
+
if not records:
|
| 182 |
+
return None
|
| 183 |
+
|
| 184 |
+
# Use gene index if available
|
| 185 |
+
if not hasattr(self, '_gene_index'):
|
| 186 |
+
self._build_gene_index(records)
|
| 187 |
+
|
| 188 |
+
# Try to get location from index
|
| 189 |
+
if gene_identifier in self._gene_index:
|
| 190 |
+
record_id, feature = self._gene_index[gene_identifier]
|
| 191 |
+
for record in records:
|
| 192 |
+
if record.id == record_id:
|
| 193 |
+
sequence = str(feature.extract(record.seq))
|
| 194 |
+
feature_info = self._get_feature_info(feature)
|
| 195 |
+
|
| 196 |
+
result = {
|
| 197 |
+
'sequence': sequence,
|
| 198 |
+
'info': feature_info
|
| 199 |
}
|
| 200 |
+
|
| 201 |
+
self._feature_cache[cache_key] = result
|
| 202 |
+
return result
|
| 203 |
+
|
| 204 |
return None
|
| 205 |
+
|
| 206 |
except Exception as e:
|
| 207 |
self.logger.error(f"Error in get_gene_data: {str(e)}")
|
| 208 |
return None
|
| 209 |
|
| 210 |
+
def _build_gene_index(self, records):
|
| 211 |
+
"""Build an index of genes for faster lookup"""
|
| 212 |
+
self._gene_index = {}
|
| 213 |
+
try:
|
| 214 |
+
for record in records:
|
| 215 |
+
for feature in record.features:
|
| 216 |
+
if feature.type == 'gene':
|
| 217 |
+
gene_name = self._get_feature_name(feature)
|
| 218 |
+
gene_id = self._get_feature_id(feature)
|
| 219 |
+
if gene_name != "N/A":
|
| 220 |
+
self._gene_index[gene_name] = (record.id, feature)
|
| 221 |
+
if gene_id != "N/A":
|
| 222 |
+
self._gene_index[gene_id] = (record.id, feature)
|
| 223 |
+
except Exception as e:
|
| 224 |
+
self.logger.error(f"Error building gene index: {str(e)}")
|
| 225 |
+
|
| 226 |
def _parse_available_genes(self):
|
| 227 |
self.available_genes = []
|
| 228 |
try:
|
|
@@ -1,27 +1,129 @@
|
|
| 1 |
from utils.sequence_utils import SeqTranslate
|
| 2 |
-
import
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
# Use: Use as a parser for the cspr files
|
| 6 |
-
# Precondition: Only to the used with .cspr files. Will not work with any other files
|
| 7 |
-
# This class also took some of the parsing functions from with classes (Multitargeting and Results) and stores them in here
|
| 8 |
-
##################################################################################################################################
|
| 9 |
|
| 10 |
class CSPRparser:
|
| 11 |
def __init__(self, inputFileName, casper_info_path):
|
| 12 |
self.fileName = inputFileName
|
|
|
|
| 13 |
self.seqTrans = SeqTranslate(casper_info_path)
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
-
def
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
for
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
break
|
| 23 |
-
|
| 24 |
-
if
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from utils.sequence_utils import SeqTranslate
|
| 2 |
+
import logging
|
| 3 |
+
from multiprocessing import Pool, cpu_count
|
| 4 |
+
from functools import partial
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
class CSPRparser:
|
| 7 |
def __init__(self, inputFileName, casper_info_path):
|
| 8 |
self.fileName = inputFileName
|
| 9 |
+
self.filename = inputFileName
|
| 10 |
self.seqTrans = SeqTranslate(casper_info_path)
|
| 11 |
+
self.logger = logging.getLogger(__name__)
|
| 12 |
+
self._line_buffer = [] # Pre-allocate buffer for lines
|
| 13 |
+
self._cached_results = {}
|
| 14 |
|
| 15 |
+
def read_targets_batch(self, chromosome, targets, endonuclease):
|
| 16 |
+
"""Ultra-fast target reading using direct tuple creation"""
|
| 17 |
+
try:
|
| 18 |
+
# Pre-process targets into a sorted list of ranges for faster lookup
|
| 19 |
+
target_ranges = []
|
| 20 |
+
for t in targets:
|
| 21 |
+
start = t['start']
|
| 22 |
+
end = t['end']
|
| 23 |
+
target_ranges.append((start, end, t['feature_name']))
|
| 24 |
+
target_ranges.sort() # Sort by start position
|
| 25 |
+
|
| 26 |
+
# Pre-allocate results list
|
| 27 |
+
results = []
|
| 28 |
+
results_append = results.append
|
| 29 |
+
|
| 30 |
+
# Read file in binary mode for speed
|
| 31 |
+
with open(self.fileName, 'rb') as f:
|
| 32 |
+
# Skip header
|
| 33 |
+
for _ in range(3):
|
| 34 |
+
f.readline()
|
| 35 |
+
|
| 36 |
+
# Find chromosome section
|
| 37 |
+
header = False
|
| 38 |
+
for line in f:
|
| 39 |
+
if b'>' in line and str(chromosome).encode() in line:
|
| 40 |
+
header = True
|
| 41 |
+
break
|
| 42 |
+
|
| 43 |
+
# Read targets
|
| 44 |
+
if header:
|
| 45 |
+
current_range_idx = 0
|
| 46 |
+
max_ranges = len(target_ranges)
|
| 47 |
+
|
| 48 |
+
while current_range_idx < max_ranges:
|
| 49 |
+
line = f.readline()
|
| 50 |
+
if not line or line.startswith(b'>'):
|
| 51 |
break
|
| 52 |
+
|
| 53 |
+
if not line.strip():
|
| 54 |
+
continue
|
| 55 |
+
|
| 56 |
+
# Fast string splitting without decode
|
| 57 |
+
parts = line.strip().split(b',')
|
| 58 |
+
if not parts:
|
| 59 |
+
continue
|
| 60 |
+
|
| 61 |
+
try:
|
| 62 |
+
pos = int(parts[0])
|
| 63 |
+
abs_pos = abs(pos)
|
| 64 |
+
|
| 65 |
+
# Get current target range
|
| 66 |
+
start, end, feature_name = target_ranges[current_range_idx]
|
| 67 |
+
|
| 68 |
+
# Skip if position is past current range
|
| 69 |
+
if abs_pos >= end:
|
| 70 |
+
current_range_idx += 1
|
| 71 |
+
continue
|
| 72 |
+
|
| 73 |
+
# Check if position is in range
|
| 74 |
+
if start <= abs_pos < end:
|
| 75 |
+
sequence = parts[1].decode()
|
| 76 |
+
pam = sequence[-3:]
|
| 77 |
+
target_seq = sequence[:-3]
|
| 78 |
+
|
| 79 |
+
results_append({
|
| 80 |
+
'feature_name': feature_name,
|
| 81 |
+
'chromosome': chromosome,
|
| 82 |
+
'position': abs_pos,
|
| 83 |
+
'location': f"{abs_pos}-{abs_pos + 23}",
|
| 84 |
+
'sequence': target_seq,
|
| 85 |
+
'pam': pam,
|
| 86 |
+
'strand': "-" if pos < 0 else "+",
|
| 87 |
+
'score': float(parts[3]) if len(parts) > 3 else 0.0,
|
| 88 |
+
'endonuclease': endonuclease
|
| 89 |
+
})
|
| 90 |
+
|
| 91 |
+
except (ValueError, IndexError):
|
| 92 |
+
continue
|
| 93 |
+
|
| 94 |
+
return results
|
| 95 |
+
|
| 96 |
+
except Exception as e:
|
| 97 |
+
self.logger.error(f"Error in read_targets_batch: {str(e)}")
|
| 98 |
+
return []
|
| 99 |
+
|
| 100 |
+
def parse_targets(self, file_path, region):
|
| 101 |
+
"""Parse targets with parallel processing and caching"""
|
| 102 |
+
cache_key = f"{file_path}:{region}"
|
| 103 |
+
if cache_key in self._cached_results:
|
| 104 |
+
return self._cached_results[cache_key]
|
| 105 |
+
|
| 106 |
+
# Split the region into chunks for parallel processing
|
| 107 |
+
chunks = self._split_region(region)
|
| 108 |
+
|
| 109 |
+
with Pool() as pool:
|
| 110 |
+
results = pool.map(partial(self._parse_chunk, file_path), chunks)
|
| 111 |
+
|
| 112 |
+
# Combine results
|
| 113 |
+
combined_targets = []
|
| 114 |
+
for chunk_result in results:
|
| 115 |
+
combined_targets.extend(chunk_result)
|
| 116 |
+
|
| 117 |
+
self._cached_results[cache_key] = combined_targets
|
| 118 |
+
return combined_targets
|
| 119 |
+
|
| 120 |
+
def _split_region(self, region):
|
| 121 |
+
"""Split a region into chunks for parallel processing"""
|
| 122 |
+
start, end = region
|
| 123 |
+
chunk_size = (end - start) // cpu_count()
|
| 124 |
+
chunks = []
|
| 125 |
+
for i in range(cpu_count()):
|
| 126 |
+
chunk_start = start + (i * chunk_size)
|
| 127 |
+
chunk_end = chunk_start + chunk_size if i < cpu_count()-1 else end
|
| 128 |
+
chunks.append((chunk_start, chunk_end))
|
| 129 |
+
return chunks
|
|
@@ -1,5 +1,8 @@
|
|
| 1 |
import os
|
| 2 |
from PyQt6.QtCore import QObject, pyqtSignal, QFileSystemWatcher
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
class DatabaseManager(QObject):
|
| 5 |
db_state_updated = pyqtSignal(bool, str, list) # Combined signal
|
|
@@ -115,9 +118,19 @@ class DatabaseManager(QObject):
|
|
| 115 |
def _on_directory_changed(self, path):
|
| 116 |
"""Handle changes in the watched directory."""
|
| 117 |
self.logger.debug(f"Detected change in directory: {path}")
|
| 118 |
-
|
|
|
|
| 119 |
is_valid, message = self.validate_db_path(path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
self.db_state_updated.emit(is_valid, message, cspr_files)
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
def _get_cspr_files(self):
|
| 123 |
"""Get a list of CSPR files in the current database directory."""
|
|
@@ -125,6 +138,15 @@ class DatabaseManager(QObject):
|
|
| 125 |
return []
|
| 126 |
return [f for f in os.listdir(self.db_path) if f.endswith('.cspr')]
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
def check_db_state(self):
|
| 129 |
"""Check the current state of the database and emit signals if changed."""
|
| 130 |
self.logger.debug("Checking database state")
|
|
@@ -135,8 +157,273 @@ class DatabaseManager(QObject):
|
|
| 135 |
self.logger.debug(f"Database state: valid={is_valid}, message={message}")
|
| 136 |
|
| 137 |
cspr_files = self._get_cspr_files()
|
| 138 |
-
self.
|
| 139 |
|
| 140 |
-
message = f"Database is valid. Contains {len(cspr_files)} CSPR files."
|
| 141 |
|
| 142 |
self.db_state_updated.emit(is_valid, message, cspr_files)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
from PyQt6.QtCore import QObject, pyqtSignal, QFileSystemWatcher
|
| 3 |
+
import sqlite3
|
| 4 |
+
from collections import Counter
|
| 5 |
+
import statistics
|
| 6 |
|
| 7 |
class DatabaseManager(QObject):
|
| 8 |
db_state_updated = pyqtSignal(bool, str, list) # Combined signal
|
|
|
|
| 118 |
def _on_directory_changed(self, path):
|
| 119 |
"""Handle changes in the watched directory."""
|
| 120 |
self.logger.debug(f"Detected change in directory: {path}")
|
| 121 |
+
|
| 122 |
+
# Get current state
|
| 123 |
is_valid, message = self.validate_db_path(path)
|
| 124 |
+
|
| 125 |
+
# Get list of files
|
| 126 |
+
cspr_files = self._get_cspr_files()
|
| 127 |
+
gbff_files = self._get_gbff_files() # Add method to get GBFF files
|
| 128 |
+
|
| 129 |
+
# Emit the signal with updated state
|
| 130 |
self.db_state_updated.emit(is_valid, message, cspr_files)
|
| 131 |
+
|
| 132 |
+
# Log the change
|
| 133 |
+
self.logger.info(f"Database state updated - Valid: {is_valid}, Files: {len(cspr_files)} CSPR, {len(gbff_files)} GBFF")
|
| 134 |
|
| 135 |
def _get_cspr_files(self):
|
| 136 |
"""Get a list of CSPR files in the current database directory."""
|
|
|
|
| 138 |
return []
|
| 139 |
return [f for f in os.listdir(self.db_path) if f.endswith('.cspr')]
|
| 140 |
|
| 141 |
+
def _get_gbff_files(self):
|
| 142 |
+
"""Get a list of GBFF files in the database directory."""
|
| 143 |
+
if not self.db_path or not os.path.isdir(self.db_path):
|
| 144 |
+
return []
|
| 145 |
+
gbff_path = os.path.join(self.db_path, 'GBFF')
|
| 146 |
+
if not os.path.exists(gbff_path):
|
| 147 |
+
return []
|
| 148 |
+
return [f for f in os.listdir(gbff_path) if f.endswith('.gbff')]
|
| 149 |
+
|
| 150 |
def check_db_state(self):
|
| 151 |
"""Check the current state of the database and emit signals if changed."""
|
| 152 |
self.logger.debug("Checking database state")
|
|
|
|
| 157 |
self.logger.debug(f"Database state: valid={is_valid}, message={message}")
|
| 158 |
|
| 159 |
cspr_files = self._get_cspr_files()
|
| 160 |
+
gbff_files = self._get_gbff_files()
|
| 161 |
|
| 162 |
+
message = f"Database is valid. Contains {len(cspr_files)} CSPR files and {len(gbff_files)} GBFF files."
|
| 163 |
|
| 164 |
self.db_state_updated.emit(is_valid, message, cspr_files)
|
| 165 |
+
self.logger.info(f"Database state checked - Valid: {is_valid}, Files: {len(cspr_files)} CSPR, {len(gbff_files)} GBFF")
|
| 166 |
+
|
| 167 |
+
def get_organisms_and_endos(self):
|
| 168 |
+
"""Get mapping of organisms to their endonucleases and files"""
|
| 169 |
+
try:
|
| 170 |
+
if not self.db_path or not os.path.exists(self.db_path):
|
| 171 |
+
self.logger.error(f"Invalid database path: {self.db_path}")
|
| 172 |
+
return {}, {}
|
| 173 |
+
|
| 174 |
+
onlyfiles = [f for f in os.listdir(self.db_path) if os.path.isfile(os.path.join(self.db_path, f))]
|
| 175 |
+
cspr_files = [f for f in onlyfiles if f.endswith('.cspr')]
|
| 176 |
+
|
| 177 |
+
organisms_to_files = {}
|
| 178 |
+
organisms_to_endos = {}
|
| 179 |
+
|
| 180 |
+
for file in cspr_files:
|
| 181 |
+
try:
|
| 182 |
+
# Parse filename
|
| 183 |
+
newname = file[0:-5] # Remove .cspr - changed from -4 to -5
|
| 184 |
+
endo = newname[newname.rfind("_") + 1:] # Get endonuclease name
|
| 185 |
+
|
| 186 |
+
# Read organism name from first line of CSPR file
|
| 187 |
+
file_path = os.path.join(self.db_path, file)
|
| 188 |
+
with open(file_path, 'r') as hold:
|
| 189 |
+
buf = hold.readline().strip()
|
| 190 |
+
species = buf.replace("GENOME: ", "")
|
| 191 |
+
|
| 192 |
+
# Store file mappings
|
| 193 |
+
if species in organisms_to_files:
|
| 194 |
+
organisms_to_files[species][endo] = [file, file.replace(".cspr", "_repeats.db")]
|
| 195 |
+
else:
|
| 196 |
+
organisms_to_files[species] = {}
|
| 197 |
+
organisms_to_files[species][endo] = [file, file.replace(".cspr", "_repeats.db")]
|
| 198 |
+
|
| 199 |
+
# Store endonuclease mappings
|
| 200 |
+
if species in organisms_to_endos:
|
| 201 |
+
if endo not in organisms_to_endos[species]:
|
| 202 |
+
organisms_to_endos[species].append(endo)
|
| 203 |
+
else:
|
| 204 |
+
organisms_to_endos[species] = [endo]
|
| 205 |
+
|
| 206 |
+
except Exception as e:
|
| 207 |
+
self.logger.error(f"Error processing file {file}: {str(e)}")
|
| 208 |
+
continue
|
| 209 |
+
|
| 210 |
+
return organisms_to_files, organisms_to_endos
|
| 211 |
+
|
| 212 |
+
except Exception as e:
|
| 213 |
+
self.logger.error(f"Error getting organisms and endonucleases: {str(e)}")
|
| 214 |
+
return {}, {}
|
| 215 |
+
|
| 216 |
+
def get_repeats_data(self, db_file, row_limit=-1):
|
| 217 |
+
"""Get repeats data for the seeds table"""
|
| 218 |
+
try:
|
| 219 |
+
conn = sqlite3.connect(db_file)
|
| 220 |
+
c = conn.cursor()
|
| 221 |
+
|
| 222 |
+
if row_limit == -1:
|
| 223 |
+
sql_query = "SELECT * FROM repeats ORDER BY count DESC;"
|
| 224 |
+
else:
|
| 225 |
+
sql_query = f"SELECT * FROM repeats ORDER BY count DESC LIMIT 0, {row_limit};"
|
| 226 |
+
|
| 227 |
+
repeats = c.execute(sql_query).fetchall()
|
| 228 |
+
processed_data = []
|
| 229 |
+
|
| 230 |
+
for repeat in repeats:
|
| 231 |
+
# Extract repeat info
|
| 232 |
+
seed = repeat[0]
|
| 233 |
+
chroms = repeat[1].split(",")
|
| 234 |
+
locs = repeat[2].split(",")
|
| 235 |
+
threes = repeat[3].split(",")
|
| 236 |
+
fives = repeat[4].split(",")
|
| 237 |
+
pams = repeat[5].split(",")
|
| 238 |
+
scores = repeat[6].split(",")
|
| 239 |
+
count = repeat[7]
|
| 240 |
+
|
| 241 |
+
# Handle missing data in threes/fives
|
| 242 |
+
if len(threes) < len(fives):
|
| 243 |
+
threes.extend([''] * (len(fives) - len(threes)))
|
| 244 |
+
elif len(fives) < len(threes):
|
| 245 |
+
fives.extend([''] * (len(threes) - len(fives)))
|
| 246 |
+
|
| 247 |
+
# Find majority sequence
|
| 248 |
+
majority_index = 0
|
| 249 |
+
three_prime, five_prime, both_prime = False, False, False
|
| 250 |
+
if threes[0] == '':
|
| 251 |
+
majority = max(set(fives), key=fives.count)
|
| 252 |
+
majority_index = fives.index(majority)
|
| 253 |
+
five_prime = True
|
| 254 |
+
elif fives[0] == '':
|
| 255 |
+
majority = max(set(threes), key=threes.count)
|
| 256 |
+
majority_index = threes.index(majority)
|
| 257 |
+
three_prime = True
|
| 258 |
+
else:
|
| 259 |
+
# account for both 3 and 5 present
|
| 260 |
+
threes_and_fives = []
|
| 261 |
+
for i in range(len(threes)):
|
| 262 |
+
threes_and_fives.append(threes[i] + fives[i])
|
| 263 |
+
majority = max(set(threes_and_fives), key=threes_and_fives.count)
|
| 264 |
+
majority_index = threes_and_fives.index(majority)
|
| 265 |
+
both_prime = True
|
| 266 |
+
|
| 267 |
+
# Calculate average repeats per scaffold
|
| 268 |
+
location_repeat_counts = Counter(chroms)
|
| 269 |
+
avg_rep_per_scaff = sum(location_repeat_counts.values()) / len(location_repeat_counts.values())
|
| 270 |
+
avg_rep_per_scaff = float("%.2f" % avg_rep_per_scaff)
|
| 271 |
+
|
| 272 |
+
# Calculate consensus percentage
|
| 273 |
+
if five_prime:
|
| 274 |
+
percent_consensus = (fives.count(fives[majority_index]) / len(fives)) * 100
|
| 275 |
+
elif three_prime:
|
| 276 |
+
percent_consensus = (threes.count(threes[majority_index]) / len(threes)) * 100
|
| 277 |
+
elif both_prime:
|
| 278 |
+
percent_consensus = (threes_and_fives.count(threes_and_fives[majority_index]) / len(threes_and_fives)) * 100
|
| 279 |
+
percent_consensus = float("%.2f" % percent_consensus)
|
| 280 |
+
|
| 281 |
+
# Determine strand
|
| 282 |
+
strand = "+" if int(locs[majority_index]) >= 0 else "-"
|
| 283 |
+
|
| 284 |
+
# Create processed row
|
| 285 |
+
processed_row = (
|
| 286 |
+
seed, # Seed
|
| 287 |
+
count, # Total Repeats
|
| 288 |
+
avg_rep_per_scaff, # Avg. Repeats/Scaffold
|
| 289 |
+
fives[majority_index] + seed + threes[majority_index], # Consensus Sequence
|
| 290 |
+
percent_consensus, # % Consensus
|
| 291 |
+
scores[majority_index], # Score
|
| 292 |
+
pams[majority_index], # PAM
|
| 293 |
+
strand # Strand
|
| 294 |
+
)
|
| 295 |
+
processed_data.append(processed_row)
|
| 296 |
+
|
| 297 |
+
conn.close()
|
| 298 |
+
return processed_data
|
| 299 |
+
|
| 300 |
+
except Exception as e:
|
| 301 |
+
self.logger.error(f"Error getting repeats data: {str(e)}")
|
| 302 |
+
raise
|
| 303 |
+
|
| 304 |
+
def get_seed_data(self, db_file, seed):
|
| 305 |
+
"""Get detailed data for a specific seed"""
|
| 306 |
+
try:
|
| 307 |
+
conn = sqlite3.connect(db_file)
|
| 308 |
+
c = conn.cursor()
|
| 309 |
+
data = c.execute("""
|
| 310 |
+
SELECT chromosome, location, pam, score,
|
| 311 |
+
five, three
|
| 312 |
+
FROM repeats
|
| 313 |
+
WHERE seed = ?
|
| 314 |
+
""", (seed,)).fetchall()
|
| 315 |
+
conn.close()
|
| 316 |
+
return data
|
| 317 |
+
except Exception as e:
|
| 318 |
+
self.logger.error(f"Error getting seed data: {str(e)}")
|
| 319 |
+
raise
|
| 320 |
+
|
| 321 |
+
def get_chro_bar_data(self, db_file, seed):
|
| 322 |
+
"""Get chromosome distribution data for a seed"""
|
| 323 |
+
try:
|
| 324 |
+
conn = sqlite3.connect(db_file)
|
| 325 |
+
c = conn.cursor()
|
| 326 |
+
data = c.execute("SELECT chromosome FROM repeats WHERE seed = ?", (seed,)).fetchone()
|
| 327 |
+
conn.close()
|
| 328 |
+
|
| 329 |
+
if not data:
|
| 330 |
+
return Counter()
|
| 331 |
+
|
| 332 |
+
# Split the chromosome string and convert to integers
|
| 333 |
+
chromosomes = [int(x) for x in data[0].split(',')]
|
| 334 |
+
counts = Counter(chromosomes)
|
| 335 |
+
return counts
|
| 336 |
+
|
| 337 |
+
except Exception as e:
|
| 338 |
+
self.logger.error(f"Error getting chromosome bar data: {str(e)}")
|
| 339 |
+
raise
|
| 340 |
+
|
| 341 |
+
def get_seeds_vs_repeats_data(self, db_file):
|
| 342 |
+
"""Get data for seeds vs repeats plot"""
|
| 343 |
+
try:
|
| 344 |
+
conn = sqlite3.connect(db_file)
|
| 345 |
+
c = conn.cursor()
|
| 346 |
+
|
| 347 |
+
# Count how many sequences have each repeat count
|
| 348 |
+
data = c.execute("""
|
| 349 |
+
SELECT count, COUNT(*) as num_sequences
|
| 350 |
+
FROM repeats
|
| 351 |
+
GROUP BY count
|
| 352 |
+
ORDER BY count ASC
|
| 353 |
+
""").fetchall()
|
| 354 |
+
|
| 355 |
+
conn.close()
|
| 356 |
+
|
| 357 |
+
if not data:
|
| 358 |
+
return None
|
| 359 |
+
|
| 360 |
+
# Separate into x and y values
|
| 361 |
+
x_vals = [row[0] for row in data] # number of repeats
|
| 362 |
+
y_vals = [row[1] for row in data] # number of sequences with that count
|
| 363 |
+
|
| 364 |
+
return {
|
| 365 |
+
'x_vals': x_vals,
|
| 366 |
+
'y_vals': y_vals
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
except Exception as e:
|
| 370 |
+
self.logger.error(f"Error getting seeds vs repeats data: {str(e)}")
|
| 371 |
+
raise
|
| 372 |
+
|
| 373 |
+
def get_repeats_vs_seeds_data(self, db_file):
|
| 374 |
+
"""Get data for repeats vs seeds plot"""
|
| 375 |
+
try:
|
| 376 |
+
self.logger.debug(f"Getting repeats vs seeds data from {db_file}")
|
| 377 |
+
|
| 378 |
+
conn = sqlite3.connect(db_file)
|
| 379 |
+
c = conn.cursor()
|
| 380 |
+
|
| 381 |
+
# Get all count values ordered by rowid to maintain order
|
| 382 |
+
self.logger.debug("Executing SQL query")
|
| 383 |
+
data = c.execute("SELECT count FROM repeats ORDER BY rowid;").fetchall()
|
| 384 |
+
self.logger.debug(f"Raw data from database: {data[:5]}...") # Log first 5 entries
|
| 385 |
+
|
| 386 |
+
conn.close()
|
| 387 |
+
|
| 388 |
+
# Extract counts from tuples
|
| 389 |
+
counts = [row[0] for row in data]
|
| 390 |
+
self.logger.debug(f"Processed counts (first 5): {counts[:5]}...")
|
| 391 |
+
|
| 392 |
+
if not counts:
|
| 393 |
+
self.logger.warning("No count data found")
|
| 394 |
+
return None
|
| 395 |
+
|
| 396 |
+
# Calculate statistics
|
| 397 |
+
stats = {
|
| 398 |
+
'average': statistics.mean(counts),
|
| 399 |
+
'median': statistics.median(counts),
|
| 400 |
+
'mode': statistics.mode(counts),
|
| 401 |
+
'repeat_count': len(counts)
|
| 402 |
+
}
|
| 403 |
+
self.logger.debug(f"Calculated statistics: {stats}")
|
| 404 |
+
|
| 405 |
+
result = {
|
| 406 |
+
'counts': counts,
|
| 407 |
+
'stats': stats
|
| 408 |
+
}
|
| 409 |
+
self.logger.debug("Successfully prepared repeats vs seeds data")
|
| 410 |
+
return result
|
| 411 |
+
|
| 412 |
+
except Exception as e:
|
| 413 |
+
self.logger.error(f"Error getting repeats vs seeds data: {str(e)}")
|
| 414 |
+
raise
|
| 415 |
+
|
| 416 |
+
def calculate_statistics(self, db_file):
|
| 417 |
+
"""Calculate global statistics"""
|
| 418 |
+
try:
|
| 419 |
+
conn = sqlite3.connect(db_file)
|
| 420 |
+
c = conn.cursor()
|
| 421 |
+
|
| 422 |
+
stats = {}
|
| 423 |
+
# Add your statistics calculations here
|
| 424 |
+
|
| 425 |
+
conn.close()
|
| 426 |
+
return stats
|
| 427 |
+
except Exception as e:
|
| 428 |
+
self.logger.error(f"Error calculating statistics: {str(e)}")
|
| 429 |
+
raise
|
|
@@ -2,72 +2,106 @@ from models.HomeWindowModel import HomeWindowModel
|
|
| 2 |
from models.CSPRparser import CSPRparser
|
| 3 |
from models.AnnotationParser import AnnotationParser
|
| 4 |
import os
|
|
|
|
| 5 |
|
| 6 |
class FindTargetsModel(HomeWindowModel):
|
| 7 |
def __init__(self, global_settings):
|
| 8 |
super().__init__(global_settings)
|
| 9 |
self.results = {}
|
| 10 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
def find_targets(self, input_data):
|
| 13 |
self.global_settings.logger.debug(f"Received input data: {input_data}")
|
| 14 |
-
print(f"Received input data: {input_data}")
|
| 15 |
|
| 16 |
organism = input_data['organism']
|
| 17 |
endo = input_data['endonuclease']
|
| 18 |
-
|
| 19 |
org_files = self.get_organism_to_files()
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
self.global_settings.logger.error(error_msg)
|
| 24 |
-
raise ValueError(error_msg)
|
| 25 |
-
|
| 26 |
-
if endo not in org_files[organism]:
|
| 27 |
-
error_msg = f"Endonuclease '{endo}' not found for organism '{organism}'. Available endonucleases: {list(org_files[organism].keys())}"
|
| 28 |
-
self.global_settings.logger.error(error_msg)
|
| 29 |
-
raise ValueError(error_msg)
|
| 30 |
|
|
|
|
| 31 |
file_path = os.path.join(self.global_settings.get_db_path(), org_files[organism][endo][0])
|
|
|
|
| 32 |
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
elif input_data['search_type'] == 'position':
|
| 38 |
-
self.results = self.find_targets_by_position(parser, input_data)
|
| 39 |
-
elif input_data['search_type'] == 'sequence':
|
| 40 |
-
self.results = self.find_targets_by_sequence(parser, input_data)
|
| 41 |
-
else:
|
| 42 |
error_msg = f"Invalid search type: {input_data['search_type']}"
|
| 43 |
self.global_settings.logger.error(error_msg)
|
| 44 |
raise ValueError(error_msg)
|
| 45 |
|
|
|
|
| 46 |
return self.results
|
| 47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
def find_targets_by_feature(self, parser, input_data):
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
| 50 |
search_query = input_data['search_query'].strip().lower()
|
| 51 |
|
|
|
|
|
|
|
| 52 |
annotation_file_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
|
| 53 |
-
|
| 54 |
|
| 55 |
try:
|
| 56 |
-
results_list =
|
| 57 |
self.global_settings.logger.debug(f"Genbank search results: {results_list}")
|
| 58 |
except Exception as e:
|
| 59 |
self.global_settings.logger.error(f"Error in genbank_search: {str(e)}")
|
| 60 |
return []
|
| 61 |
|
|
|
|
|
|
|
| 62 |
formatted_results = []
|
|
|
|
|
|
|
| 63 |
for chrom, feature in results_list:
|
| 64 |
if feature.type in ['CDS']:
|
| 65 |
feature_info = self._get_feature_info(feature)
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
formatted_results.append({
|
| 72 |
'feature_type': feature.type,
|
| 73 |
'chromosome': chrom,
|
|
|
|
| 2 |
from models.CSPRparser import CSPRparser
|
| 3 |
from models.AnnotationParser import AnnotationParser
|
| 4 |
import os
|
| 5 |
+
from functools import lru_cache
|
| 6 |
|
| 7 |
class FindTargetsModel(HomeWindowModel):
|
| 8 |
def __init__(self, global_settings):
|
| 9 |
super().__init__(global_settings)
|
| 10 |
self.results = {}
|
| 11 |
+
self._parser_cache = {} # Cache for CSPRparser instances
|
| 12 |
+
self.global_settings.annotation_file_changed.connect(self._on_annotation_file_changed)
|
| 13 |
+
|
| 14 |
+
def _on_annotation_file_changed(self, new_annotation_file):
|
| 15 |
+
"""Clear caches when annotation file changes"""
|
| 16 |
+
self.global_settings.logger.debug(f"FindTargetsModel clearing caches for new annotation file: {new_annotation_file}")
|
| 17 |
+
self._parser_cache.clear()
|
| 18 |
+
|
| 19 |
+
@lru_cache(maxsize=32)
|
| 20 |
+
def _get_parser(self, file_path):
|
| 21 |
+
"""Cache CSPRparser instances for reuse"""
|
| 22 |
+
if file_path not in self._parser_cache:
|
| 23 |
+
self._parser_cache[file_path] = CSPRparser(file_path, self.global_settings.get_casper_info_path())
|
| 24 |
+
return self._parser_cache[file_path]
|
| 25 |
|
| 26 |
def find_targets(self, input_data):
|
| 27 |
self.global_settings.logger.debug(f"Received input data: {input_data}")
|
|
|
|
| 28 |
|
| 29 |
organism = input_data['organism']
|
| 30 |
endo = input_data['endonuclease']
|
|
|
|
| 31 |
org_files = self.get_organism_to_files()
|
| 32 |
|
| 33 |
+
# Validate input data
|
| 34 |
+
self._validate_input(organism, endo, org_files)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
+
# Get file path and parser
|
| 37 |
file_path = os.path.join(self.global_settings.get_db_path(), org_files[organism][endo][0])
|
| 38 |
+
parser = self._get_parser(file_path)
|
| 39 |
|
| 40 |
+
# Use dictionary for faster lookup
|
| 41 |
+
search_types = {
|
| 42 |
+
'feature': self.find_targets_by_feature,
|
| 43 |
+
'position': self.find_targets_by_position,
|
| 44 |
+
'sequence': self.find_targets_by_sequence
|
| 45 |
+
}
|
| 46 |
|
| 47 |
+
search_func = search_types.get(input_data['search_type'])
|
| 48 |
+
if not search_func:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
error_msg = f"Invalid search type: {input_data['search_type']}"
|
| 50 |
self.global_settings.logger.error(error_msg)
|
| 51 |
raise ValueError(error_msg)
|
| 52 |
|
| 53 |
+
self.results = search_func(parser, input_data)
|
| 54 |
return self.results
|
| 55 |
|
| 56 |
+
def _validate_input(self, organism, endo, org_files):
|
| 57 |
+
"""Validate input parameters"""
|
| 58 |
+
if organism not in org_files:
|
| 59 |
+
error_msg = f"Organism '{organism}' not found in the database. Available organisms: {list(org_files.keys())}"
|
| 60 |
+
self.global_settings.logger.error(error_msg)
|
| 61 |
+
raise ValueError(error_msg)
|
| 62 |
+
|
| 63 |
+
if endo not in org_files[organism]:
|
| 64 |
+
error_msg = f"Endonuclease '{endo}' not found for organism '{organism}'. Available endonucleases: {list(org_files[organism].keys())}"
|
| 65 |
+
self.global_settings.logger.error(error_msg)
|
| 66 |
+
raise ValueError(error_msg)
|
| 67 |
+
|
| 68 |
def find_targets_by_feature(self, parser, input_data):
|
| 69 |
+
# Get annotation file from input data or global settings
|
| 70 |
+
annotation_file = (input_data.get('annotation_file') or
|
| 71 |
+
self.global_settings.get_current_annotation_file())
|
| 72 |
+
|
| 73 |
search_query = input_data['search_query'].strip().lower()
|
| 74 |
|
| 75 |
+
# Create new annotation parser instance for each search
|
| 76 |
+
annotation_parser = AnnotationParser(self.global_settings)
|
| 77 |
annotation_file_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
|
| 78 |
+
annotation_parser.set_annotation_file(annotation_file_path)
|
| 79 |
|
| 80 |
try:
|
| 81 |
+
results_list = annotation_parser.genbank_search([search_query])
|
| 82 |
self.global_settings.logger.debug(f"Genbank search results: {results_list}")
|
| 83 |
except Exception as e:
|
| 84 |
self.global_settings.logger.error(f"Error in genbank_search: {str(e)}")
|
| 85 |
return []
|
| 86 |
|
| 87 |
+
# Use a set for faster lookups
|
| 88 |
+
search_terms = {search_query}
|
| 89 |
formatted_results = []
|
| 90 |
+
|
| 91 |
+
# Pre-calculate feature info once for each feature
|
| 92 |
for chrom, feature in results_list:
|
| 93 |
if feature.type in ['CDS']:
|
| 94 |
feature_info = self._get_feature_info(feature)
|
| 95 |
+
|
| 96 |
+
# Combine all searchable text into one string for a single search operation
|
| 97 |
+
searchable_text = ' '.join([
|
| 98 |
+
feature_info['feature_name'].lower(),
|
| 99 |
+
feature_info['feature_id'].lower(),
|
| 100 |
+
feature_info['feature_description'].lower()
|
| 101 |
+
])
|
| 102 |
+
|
| 103 |
+
# Single check if any search term is in the searchable text
|
| 104 |
+
if any(term in searchable_text for term in search_terms):
|
| 105 |
formatted_results.append({
|
| 106 |
'feature_type': feature.type,
|
| 107 |
'chromosome': chrom,
|
|
@@ -15,6 +15,7 @@ class GlobalSettings(QObject):
|
|
| 15 |
db_state_updated = pyqtSignal(bool, str, list) # Combined signal
|
| 16 |
first_time_startup = pyqtSignal() # New signal
|
| 17 |
endonuclease_updated = pyqtSignal()
|
|
|
|
| 18 |
|
| 19 |
def __init__(self, app_dir_path):
|
| 20 |
super().__init__()
|
|
@@ -45,6 +46,8 @@ class GlobalSettings(QObject):
|
|
| 45 |
|
| 46 |
self.main_window = None
|
| 47 |
|
|
|
|
|
|
|
| 48 |
def _initialize_directories(self):
|
| 49 |
self.src_dir_path = os.path.join(self.app_dir_path, 'src')
|
| 50 |
self.ui_dir_path = os.path.join(self.src_dir_path, self.config_manager.get_config_value('paths.ui'))
|
|
@@ -268,5 +271,19 @@ class GlobalSettings(QObject):
|
|
| 268 |
def get_endonucleases(self):
|
| 269 |
return self.config_manager.get_endonucleases()
|
| 270 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
# Global instance
|
| 272 |
global_settings = None
|
|
|
|
| 15 |
db_state_updated = pyqtSignal(bool, str, list) # Combined signal
|
| 16 |
first_time_startup = pyqtSignal() # New signal
|
| 17 |
endonuclease_updated = pyqtSignal()
|
| 18 |
+
annotation_file_changed = pyqtSignal(str) # New signal for annotation file changes
|
| 19 |
|
| 20 |
def __init__(self, app_dir_path):
|
| 21 |
super().__init__()
|
|
|
|
| 46 |
|
| 47 |
self.main_window = None
|
| 48 |
|
| 49 |
+
self._current_annotation_file = None
|
| 50 |
+
|
| 51 |
def _initialize_directories(self):
|
| 52 |
self.src_dir_path = os.path.join(self.app_dir_path, 'src')
|
| 53 |
self.ui_dir_path = os.path.join(self.src_dir_path, self.config_manager.get_config_value('paths.ui'))
|
|
|
|
| 271 |
def get_endonucleases(self):
|
| 272 |
return self.config_manager.get_endonucleases()
|
| 273 |
|
| 274 |
+
def set_current_annotation_file(self, annotation_file):
|
| 275 |
+
"""Set the current annotation file and notify listeners"""
|
| 276 |
+
if self._current_annotation_file != annotation_file:
|
| 277 |
+
self._current_annotation_file = annotation_file
|
| 278 |
+
self.logger.debug(f"Current annotation file changed to: {annotation_file}")
|
| 279 |
+
self.annotation_file_changed.emit(annotation_file)
|
| 280 |
+
|
| 281 |
+
def get_current_annotation_file(self):
|
| 282 |
+
"""Get the currently selected annotation file"""
|
| 283 |
+
if not self._current_annotation_file and hasattr(self, '_current_home_window'):
|
| 284 |
+
# Try to get from home window if not set
|
| 285 |
+
self._current_annotation_file = self._current_home_window.get_annotation_file()
|
| 286 |
+
return self._current_annotation_file
|
| 287 |
+
|
| 288 |
# Global instance
|
| 289 |
global_settings = None
|
|
@@ -1,140 +1,132 @@
|
|
| 1 |
import os
|
| 2 |
-
import sqlite3
|
| 3 |
-
from collections import Counter
|
| 4 |
-
import statistics
|
| 5 |
-
import models.GlobalSettings as GlobalSettings
|
| 6 |
-
from models.CSPRparser import CSPRparser
|
| 7 |
|
| 8 |
class MultitargetingWindowModel:
|
| 9 |
def __init__(self, global_settings):
|
| 10 |
-
self.
|
|
|
|
|
|
|
| 11 |
self.cspr_file = ""
|
| 12 |
self.db_file = ""
|
| 13 |
-
self.organisms_to_files = {}
|
| 14 |
-
self.organisms_to_endos = {}
|
| 15 |
-
self.chromo_length = []
|
| 16 |
-
self.max_repeats = 1
|
| 17 |
-
self.average = 0
|
| 18 |
-
self.median = 0
|
| 19 |
-
self.mode = 0
|
| 20 |
-
self.average_unique = 0
|
| 21 |
-
self.average_rep = 0
|
| 22 |
-
self.repeat_count = 0
|
| 23 |
self.row_limit = 1000
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
def load_organisms_and_endos(self):
|
| 28 |
-
# This method should populate the organisms_to_endos dictionary
|
| 29 |
-
# You'll need to implement the logic to load this data from your database or files
|
| 30 |
-
# For example:
|
| 31 |
-
# self.organisms_to_endos = {
|
| 32 |
-
# "E. coli": ["Cas9", "Cas12a"],
|
| 33 |
-
# "S. cerevisiae": ["Cas9"],
|
| 34 |
-
# # ... other organisms and their associated endonucleases
|
| 35 |
-
# }
|
| 36 |
-
pass
|
| 37 |
|
| 38 |
def get_organisms(self):
|
| 39 |
-
|
| 40 |
return list(self.organisms_to_endos.keys())
|
| 41 |
|
| 42 |
def get_endos_for_organism(self, organism):
|
| 43 |
-
|
| 44 |
return self.organisms_to_endos.get(organism, [])
|
| 45 |
|
| 46 |
def set_files(self, organism, endo):
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
| 49 |
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
kstats = line.replace("KARYSTATS: ", "").strip().split(',')[:-1]
|
| 56 |
-
break
|
| 57 |
-
return kstats
|
| 58 |
|
| 59 |
def get_repeats_data(self):
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
sql_query = "SELECT * FROM repeats ORDER BY count DESC;"
|
| 65 |
-
else:
|
| 66 |
-
sql_query = f"SELECT * FROM repeats ORDER BY count DESC LIMIT 0, {self.row_limit};"
|
| 67 |
-
|
| 68 |
-
data = c.execute(sql_query).fetchall()
|
| 69 |
-
c.close()
|
| 70 |
-
conn.close()
|
| 71 |
-
return data
|
| 72 |
|
| 73 |
def get_seed_data(self, seed):
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
data = c.execute("SELECT chromosome, location, pam, score, five, three FROM repeats WHERE seed = ?", (seed,)).fetchone()
|
| 77 |
-
c.close()
|
| 78 |
-
conn.close()
|
| 79 |
-
return data
|
| 80 |
-
|
| 81 |
-
def process_seed_data(self, seed_data, kstats):
|
| 82 |
-
chromo, pos, pam, score, five, three = seed_data
|
| 83 |
-
chromo = chromo.split(',')
|
| 84 |
-
pos = pos.split(',')
|
| 85 |
-
pam = pam.split(',')
|
| 86 |
-
score = score.split(',')
|
| 87 |
-
five = five.split(',')
|
| 88 |
-
three = three.split(',')
|
| 89 |
-
|
| 90 |
-
seed_data = {}
|
| 91 |
-
event_data = {}
|
| 92 |
-
for i in range(len(chromo)):
|
| 93 |
-
curr_chromo = int(chromo[i])
|
| 94 |
-
dir = "+" if int(pos[i]) >= 0 else "-"
|
| 95 |
-
normalized_location = abs(float(pos[i]) / float(kstats[curr_chromo - 1]))
|
| 96 |
-
if curr_chromo in seed_data:
|
| 97 |
-
seed_data[curr_chromo].append(normalized_location)
|
| 98 |
-
event_data[curr_chromo].append([normalized_location, pos[i], five[i] + seed + three[i], pam[i], score[i], dir])
|
| 99 |
-
else:
|
| 100 |
-
seed_data[curr_chromo] = [normalized_location]
|
| 101 |
-
event_data[curr_chromo] = [[normalized_location, pos[i], five[i] + seed + three[i], pam[i], score[i], dir]]
|
| 102 |
-
|
| 103 |
-
return seed_data, event_data
|
| 104 |
|
| 105 |
def get_chro_bar_data(self, seed):
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
data = c.execute("SELECT chromosome FROM repeats WHERE seed = ?", (seed,)).fetchone()
|
| 109 |
-
c.close()
|
| 110 |
-
conn.close()
|
| 111 |
-
|
| 112 |
-
data = [int(x) for x in data[0].split(',')]
|
| 113 |
-
bar_data = Counter(data)
|
| 114 |
-
return bar_data
|
| 115 |
|
| 116 |
def get_seeds_vs_repeats_data(self):
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
data = c.execute("select count, COUNT(count) as cnt from repeats group by count order by cnt DESC;").fetchall()
|
| 120 |
-
c.close()
|
| 121 |
-
conn.close()
|
| 122 |
-
return data
|
| 123 |
|
| 124 |
def get_repeats_vs_seeds_data(self):
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
-
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
class MultitargetingWindowModel:
|
| 4 |
def __init__(self, global_settings):
|
| 5 |
+
self.settings = global_settings
|
| 6 |
+
self.logger = global_settings.get_logger()
|
| 7 |
+
|
| 8 |
self.cspr_file = ""
|
| 9 |
self.db_file = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
self.row_limit = 1000
|
| 11 |
+
|
| 12 |
+
# Get organism and endo mappings from DatabaseManager
|
| 13 |
+
self.organisms_to_files, self.organisms_to_endos = self.settings.db_manager.get_organisms_and_endos()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
def get_organisms(self):
|
| 16 |
+
"""Get list of available organisms"""
|
| 17 |
return list(self.organisms_to_endos.keys())
|
| 18 |
|
| 19 |
def get_endos_for_organism(self, organism):
|
| 20 |
+
"""Get available endonucleases for given organism"""
|
| 21 |
return self.organisms_to_endos.get(organism, [])
|
| 22 |
|
| 23 |
def set_files(self, organism, endo):
|
| 24 |
+
"""Set the CSPR and DB files for analysis"""
|
| 25 |
+
if not organism or not endo:
|
| 26 |
+
self.logger.error("Organism or endonuclease not specified")
|
| 27 |
+
raise ValueError("Organism and endonuclease must be specified")
|
| 28 |
|
| 29 |
+
self.cspr_file = self._get_cspr_file_path(organism, endo)
|
| 30 |
+
self.db_file = self._get_db_file_path(organism, endo)
|
| 31 |
+
|
| 32 |
+
if not self.cspr_file or not self.db_file:
|
| 33 |
+
raise FileNotFoundError("Required files not found")
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
def get_repeats_data(self):
|
| 36 |
+
"""Get repeats data for the seeds table"""
|
| 37 |
+
if not self.db_file:
|
| 38 |
+
raise ValueError("Database file not set. Please select an organism and endonuclease first.")
|
| 39 |
+
return self.settings.db_manager.get_repeats_data(self.db_file, self.row_limit)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
def get_seed_data(self, seed):
|
| 42 |
+
"""Get detailed data for a specific seed"""
|
| 43 |
+
return self.settings.db_manager.get_seed_data(self.db_file, seed)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
def get_chro_bar_data(self, seed):
|
| 46 |
+
"""Get chromosome distribution data for a seed"""
|
| 47 |
+
return self.settings.db_manager.get_chro_bar_data(self.db_file, seed)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
def get_seeds_vs_repeats_data(self):
|
| 50 |
+
"""Get data for seeds vs repeats plot"""
|
| 51 |
+
return self.settings.db_manager.get_seeds_vs_repeats_data(self.db_file)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
def get_repeats_vs_seeds_data(self):
|
| 54 |
+
"""Get data for repeats vs seeds plot"""
|
| 55 |
+
return self.settings.db_manager.get_repeats_vs_seeds_data(self.db_file)
|
| 56 |
+
|
| 57 |
+
def calculate_statistics(self):
|
| 58 |
+
"""Calculate global statistics"""
|
| 59 |
+
return self.settings.db_manager.calculate_statistics(self.db_file)
|
| 60 |
+
|
| 61 |
+
def get_sql_settings(self):
|
| 62 |
+
"""Get current SQL query settings"""
|
| 63 |
+
return {
|
| 64 |
+
'row_limit': self.row_limit
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
def update_sql_settings(self, settings):
|
| 68 |
+
"""Update SQL query settings"""
|
| 69 |
+
if 'row_limit' in settings:
|
| 70 |
+
self.row_limit = settings['row_limit']
|
| 71 |
|
| 72 |
+
# Keep the file path methods as they are specific to this model
|
| 73 |
+
def _get_cspr_file_path(self, organism, endo):
|
| 74 |
+
"""Get path to CSPR file for organism/endo combination"""
|
| 75 |
+
try:
|
| 76 |
+
if organism not in self.organisms_to_files or endo not in self.organisms_to_files[organism]:
|
| 77 |
+
self.logger.error(f"No CSPR file mapping found for {organism} with {endo}")
|
| 78 |
+
return None
|
| 79 |
+
|
| 80 |
+
file_name = self.organisms_to_files[organism][endo][0]
|
| 81 |
+
cspr_path = os.path.join(self.settings.get_db_path(), file_name)
|
| 82 |
+
|
| 83 |
+
if not os.path.exists(cspr_path):
|
| 84 |
+
self.logger.error(f"CSPR file not found: {cspr_path}")
|
| 85 |
+
return None
|
| 86 |
+
|
| 87 |
+
return cspr_path
|
| 88 |
+
except Exception as e:
|
| 89 |
+
self.logger.error(f"Error getting CSPR file path: {str(e)}")
|
| 90 |
+
return None
|
| 91 |
+
|
| 92 |
+
def _get_db_file_path(self, organism, endo):
|
| 93 |
+
"""Get path to DB file for organism/endo combination"""
|
| 94 |
+
try:
|
| 95 |
+
if organism not in self.organisms_to_files or endo not in self.organisms_to_files[organism]:
|
| 96 |
+
self.logger.error(f"No DB file mapping found for {organism} with {endo}")
|
| 97 |
+
return None
|
| 98 |
+
|
| 99 |
+
file_name = self.organisms_to_files[organism][endo][1]
|
| 100 |
+
db_path = os.path.join(self.settings.get_db_path(), file_name)
|
| 101 |
+
|
| 102 |
+
if not os.path.exists(db_path):
|
| 103 |
+
self.logger.error(f"Database file not found: {db_path}")
|
| 104 |
+
return None
|
| 105 |
+
|
| 106 |
+
return db_path
|
| 107 |
+
except Exception as e:
|
| 108 |
+
self.logger.error(f"Error getting database file path: {str(e)}")
|
| 109 |
+
return None
|
| 110 |
+
|
| 111 |
+
def get_kstats(self):
|
| 112 |
+
"""Get kstats from CSPR file"""
|
| 113 |
+
try:
|
| 114 |
+
if not self.cspr_file:
|
| 115 |
+
self.logger.error("CSPR file not set")
|
| 116 |
+
raise ValueError("CSPR file not set. Please select an organism and endonuclease first.")
|
| 117 |
+
|
| 118 |
+
kstats = []
|
| 119 |
+
with open(self.cspr_file, "r") as f:
|
| 120 |
+
for line in f:
|
| 121 |
+
if "KARYSTATS" in line:
|
| 122 |
+
kstats = line.replace("KARYSTATS: ", "").strip().split(',')[:-1]
|
| 123 |
+
break
|
| 124 |
+
|
| 125 |
+
if not kstats:
|
| 126 |
+
raise ValueError("No KARYSTATS found in CSPR file")
|
| 127 |
+
|
| 128 |
+
return kstats
|
| 129 |
+
|
| 130 |
+
except Exception as e:
|
| 131 |
+
self.logger.error(f"Error getting kstats: {str(e)}")
|
| 132 |
+
raise
|
|
@@ -10,6 +10,8 @@ import platform
|
|
| 10 |
import warnings
|
| 11 |
import xml.etree.ElementTree as ET
|
| 12 |
from PyQt6.QtCore import Qt
|
|
|
|
|
|
|
| 13 |
|
| 14 |
class NCBIWindowModel:
|
| 15 |
def __init__(self, settings):
|
|
@@ -20,6 +22,9 @@ class NCBIWindowModel:
|
|
| 20 |
self.refseq_ftp_dict = {}
|
| 21 |
self.files = [] # Initialize the files list
|
| 22 |
Entrez.email = "your_email@example.com" # Replace with a valid email
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
# Suppress the XMLParsedAsHTMLWarning
|
| 25 |
warnings.filterwarnings("ignore", category=XMLParsedAsHTMLWarning)
|
|
@@ -230,3 +235,118 @@ class CustomProxyModel(QtCore.QSortFilterProxyModel):
|
|
| 230 |
if regex.match(text).hasMatch():
|
| 231 |
return False
|
| 232 |
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
import warnings
|
| 11 |
import xml.etree.ElementTree as ET
|
| 12 |
from PyQt6.QtCore import Qt
|
| 13 |
+
import socket
|
| 14 |
+
from urllib.parse import urlparse
|
| 15 |
|
| 16 |
class NCBIWindowModel:
|
| 17 |
def __init__(self, settings):
|
|
|
|
| 22 |
self.refseq_ftp_dict = {}
|
| 23 |
self.files = [] # Initialize the files list
|
| 24 |
Entrez.email = "your_email@example.com" # Replace with a valid email
|
| 25 |
+
|
| 26 |
+
# Make DownloadThread accessible through the model
|
| 27 |
+
self.DownloadThread = DownloadThread
|
| 28 |
|
| 29 |
# Suppress the XMLParsedAsHTMLWarning
|
| 30 |
warnings.filterwarnings("ignore", category=XMLParsedAsHTMLWarning)
|
|
|
|
| 235 |
if regex.match(text).hasMatch():
|
| 236 |
return False
|
| 237 |
return True
|
| 238 |
+
|
| 239 |
+
class DownloadThread(QtCore.QThread):
|
| 240 |
+
finished = QtCore.pyqtSignal(bool)
|
| 241 |
+
progress_updated = QtCore.pyqtSignal(int, int, int)
|
| 242 |
+
status_updated = QtCore.pyqtSignal(str)
|
| 243 |
+
all_completed = QtCore.pyqtSignal()
|
| 244 |
+
|
| 245 |
+
def __init__(self, controller, url, id, species_name, strain, download_fna, download_gbff):
|
| 246 |
+
super().__init__()
|
| 247 |
+
self.controller = controller
|
| 248 |
+
self.url = url
|
| 249 |
+
self.id = id
|
| 250 |
+
self.species_name = species_name
|
| 251 |
+
self.strain = strain
|
| 252 |
+
self.download_fna = download_fna
|
| 253 |
+
self.download_gbff = download_gbff
|
| 254 |
+
|
| 255 |
+
def run(self):
|
| 256 |
+
try:
|
| 257 |
+
parsed_url = urlparse(self.url)
|
| 258 |
+
ftp_host = parsed_url.netloc
|
| 259 |
+
ftp_path = parsed_url.path
|
| 260 |
+
|
| 261 |
+
self.controller.logger.info(f"Attempting to connect to FTP server: {ftp_host}")
|
| 262 |
+
|
| 263 |
+
try:
|
| 264 |
+
ip_address = socket.gethostbyname(ftp_host)
|
| 265 |
+
self.controller.logger.info(f"Resolved IP address: {ip_address}")
|
| 266 |
+
except socket.gaierror as e:
|
| 267 |
+
self.controller.logger.error(f"Failed to resolve hostname: {ftp_host}. Error: {str(e)}")
|
| 268 |
+
self.finished.emit(False)
|
| 269 |
+
return
|
| 270 |
+
|
| 271 |
+
ftp = FTP(ftp_host)
|
| 272 |
+
ftp.login()
|
| 273 |
+
ftp.cwd(ftp_path)
|
| 274 |
+
ftp.set_pasv(True)
|
| 275 |
+
|
| 276 |
+
# Set binary mode before any operations
|
| 277 |
+
ftp.voidcmd('TYPE I')
|
| 278 |
+
|
| 279 |
+
files_to_download = []
|
| 280 |
+
|
| 281 |
+
# Get list of all files
|
| 282 |
+
all_files = ftp.nlst()
|
| 283 |
+
|
| 284 |
+
# Process FNA files if requested
|
| 285 |
+
if self.download_fna:
|
| 286 |
+
# Find the main genomic FNA file (should be exactly one)
|
| 287 |
+
genomic_fna = [f for f in all_files
|
| 288 |
+
if f.endswith('_genomic.fna.gz')
|
| 289 |
+
and not any(x in f for x in ['cds_from', 'rna_from'])]
|
| 290 |
+
|
| 291 |
+
if genomic_fna:
|
| 292 |
+
files_to_download.append(genomic_fna[0])
|
| 293 |
+
self.controller.logger.info(f"Found main genomic FNA file: {genomic_fna[0]}")
|
| 294 |
+
|
| 295 |
+
# Process GBFF files if requested
|
| 296 |
+
if self.download_gbff:
|
| 297 |
+
gbff_files = [f for f in all_files if f.endswith('_genomic.gbff.gz')]
|
| 298 |
+
files_to_download.extend(gbff_files)
|
| 299 |
+
self.controller.logger.info(f"Found GBFF files: {gbff_files}")
|
| 300 |
+
|
| 301 |
+
# Calculate total size with error handling
|
| 302 |
+
total_size = 0
|
| 303 |
+
for file in files_to_download:
|
| 304 |
+
try:
|
| 305 |
+
size = ftp.size(file)
|
| 306 |
+
if size is not None:
|
| 307 |
+
total_size += size
|
| 308 |
+
except Exception as e:
|
| 309 |
+
self.controller.logger.warning(f"Could not get size for file {file}: {str(e)}")
|
| 310 |
+
|
| 311 |
+
downloaded_size = 0
|
| 312 |
+
|
| 313 |
+
# Download files
|
| 314 |
+
for file in files_to_download:
|
| 315 |
+
try:
|
| 316 |
+
self.status_updated.emit(f"Downloading: {file}")
|
| 317 |
+
file_type = 'FNA' if file.endswith('.fna.gz') else 'GBFF'
|
| 318 |
+
output_dir = os.path.join(self.controller.settings.CSPR_DB, file_type)
|
| 319 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 320 |
+
|
| 321 |
+
local_filename = os.path.join(output_dir, file)
|
| 322 |
+
self.controller.logger.info(f"Downloading file: {file} to {local_filename}")
|
| 323 |
+
|
| 324 |
+
with open(local_filename, 'wb') as local_file:
|
| 325 |
+
def callback(data):
|
| 326 |
+
local_file.write(data)
|
| 327 |
+
nonlocal downloaded_size
|
| 328 |
+
downloaded_size += len(data)
|
| 329 |
+
if total_size > 0:
|
| 330 |
+
self.progress_updated.emit(self.id, downloaded_size, total_size)
|
| 331 |
+
|
| 332 |
+
ftp.retrbinary(f"RETR {file}", callback)
|
| 333 |
+
|
| 334 |
+
self.controller.logger.info(f"Download complete: {file}")
|
| 335 |
+
self.status_updated.emit(f"Decompressing: {file}")
|
| 336 |
+
|
| 337 |
+
self.controller.model.decompress_file(local_filename)
|
| 338 |
+
decompressed_filename = local_filename[:-3]
|
| 339 |
+
self.controller.model.add_downloaded_file(decompressed_filename)
|
| 340 |
+
|
| 341 |
+
except Exception as e:
|
| 342 |
+
self.controller.logger.error(f"Error downloading file {file}: {str(e)}")
|
| 343 |
+
continue
|
| 344 |
+
|
| 345 |
+
ftp.quit()
|
| 346 |
+
self.controller.logger.info(f"All files downloaded and decompressed successfully for ID: {self.id}")
|
| 347 |
+
self.all_completed.emit()
|
| 348 |
+
self.finished.emit(True)
|
| 349 |
+
|
| 350 |
+
except Exception as e:
|
| 351 |
+
self.controller.logger.error(f"Download error for ID {self.id}: {str(e)}", exc_info=True)
|
| 352 |
+
self.finished.emit(False)
|
|
@@ -88,9 +88,9 @@ class NewGenomeWindowModel(QObject):
|
|
| 88 |
f'{db_path}',
|
| 89 |
f'{self.settings.get_casper_info_path()}',
|
| 90 |
f'{file_path}',
|
| 91 |
-
f'{organism_name} {strain}',
|
| 92 |
'notes',
|
| 93 |
-
f'
|
| 94 |
]
|
| 95 |
return arguments
|
| 96 |
|
|
|
|
| 88 |
f'{db_path}',
|
| 89 |
f'{self.settings.get_casper_info_path()}',
|
| 90 |
f'{file_path}',
|
| 91 |
+
f'"{organism_name} {strain}"',
|
| 92 |
'notes',
|
| 93 |
+
f'DATA:{endonuclease_data["endonuclease_on_target_scoring"]}'
|
| 94 |
]
|
| 95 |
return arguments
|
| 96 |
|
|
@@ -5,8 +5,9 @@ from utils.ui import show_error
|
|
| 5 |
|
| 6 |
class PopulationAnalysisWindowModel:
|
| 7 |
def __init__(self, global_settings):
|
| 8 |
-
self.
|
| 9 |
-
self.
|
|
|
|
| 10 |
self.cspr_files = []
|
| 11 |
self.db_files = []
|
| 12 |
self.org_names = {}
|
|
@@ -16,37 +17,75 @@ class PopulationAnalysisWindowModel:
|
|
| 16 |
self.index_to_db = {}
|
| 17 |
|
| 18 |
def load_endonucleases(self):
|
| 19 |
-
|
| 20 |
try:
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
except Exception as e:
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
def get_organism_files(self,
|
|
|
|
| 39 |
org_files = []
|
| 40 |
try:
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
except Exception as e:
|
| 49 |
-
|
|
|
|
| 50 |
return org_files
|
| 51 |
|
| 52 |
def get_shared_seeds(self, db_files, limit=False):
|
|
@@ -131,6 +170,11 @@ class PopulationAnalysisWindowModel:
|
|
| 131 |
locations = []
|
| 132 |
try:
|
| 133 |
for db_file in db_files:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
with sqlite3.connect(db_file) as conn:
|
| 135 |
c = conn.cursor()
|
| 136 |
for seed in seeds:
|
|
@@ -145,15 +189,25 @@ class PopulationAnalysisWindowModel:
|
|
| 145 |
locations.append({
|
| 146 |
'seed': seed,
|
| 147 |
'sequence': sequence,
|
| 148 |
-
'organism':
|
| 149 |
'chromosome': chrom,
|
| 150 |
'location': abs(int(locs[i]))
|
| 151 |
})
|
|
|
|
| 152 |
except Exception as e:
|
|
|
|
| 153 |
show_error(self.global_settings, "Error getting seed locations", str(e))
|
| 154 |
return locations
|
| 155 |
|
| 156 |
def get_org_names(self):
|
| 157 |
-
|
| 158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
|
|
|
|
| 5 |
|
| 6 |
class PopulationAnalysisWindowModel:
|
| 7 |
def __init__(self, global_settings):
|
| 8 |
+
self.settings = global_settings
|
| 9 |
+
self.logger = self.settings.get_logger()
|
| 10 |
+
self.app_dir = self.settings.get_app_dir_path()
|
| 11 |
self.cspr_files = []
|
| 12 |
self.db_files = []
|
| 13 |
self.org_names = {}
|
|
|
|
| 17 |
self.index_to_db = {}
|
| 18 |
|
| 19 |
def load_endonucleases(self):
|
| 20 |
+
"""Load endonucleases from GlobalSettings"""
|
| 21 |
try:
|
| 22 |
+
self.logger.info("Starting load_endonucleases()")
|
| 23 |
+
|
| 24 |
+
# Get endonucleases from global settings
|
| 25 |
+
endos = self.settings.get_endonucleases()
|
| 26 |
+
self.logger.debug(f"Raw endonucleases from settings: {endos}")
|
| 27 |
+
|
| 28 |
+
if not endos:
|
| 29 |
+
self.logger.warning("No endonucleases returned from settings")
|
| 30 |
+
return {}
|
| 31 |
+
|
| 32 |
+
# Format the endonucleases for display
|
| 33 |
+
formatted_endos = {}
|
| 34 |
+
for endo, data in endos.items():
|
| 35 |
+
self.logger.debug(f"Processing endo: {endo}, data: {data}")
|
| 36 |
+
pam = data.get('pam', '').strip()
|
| 37 |
+
# Remove any extra "PAM:" text that might be in the PAM string
|
| 38 |
+
pam = pam.replace('PAM:', '').strip()
|
| 39 |
+
# Create display name without duplicate "PAM:" text
|
| 40 |
+
display_name = f"{endo}"
|
| 41 |
+
|
| 42 |
+
formatted_endos[display_name] = (endo, pam,
|
| 43 |
+
data.get('default_five_length', ''),
|
| 44 |
+
data.get('default_seed_length', ''),
|
| 45 |
+
data.get('default_three_length', ''))
|
| 46 |
+
|
| 47 |
+
self.logger.info(f"Successfully formatted {len(formatted_endos)} endonucleases")
|
| 48 |
+
self.logger.debug(f"Formatted endonucleases: {formatted_endos}")
|
| 49 |
+
return formatted_endos
|
| 50 |
+
|
| 51 |
except Exception as e:
|
| 52 |
+
self.logger.error(f"Error loading endonucleases: {str(e)}")
|
| 53 |
+
self.logger.exception("Full traceback:")
|
| 54 |
+
show_error(self.settings, "Error loading endonucleases", str(e))
|
| 55 |
+
return {}
|
| 56 |
|
| 57 |
+
def get_organism_files(self, endo_display_name):
|
| 58 |
+
"""Get organism files for selected endonuclease using DatabaseManager"""
|
| 59 |
org_files = []
|
| 60 |
try:
|
| 61 |
+
# Extract just the endonuclease name from the display text (remove PAM)
|
| 62 |
+
endo = endo_display_name.split(" - PAM:")[0].strip()
|
| 63 |
+
self.logger.info(f"Getting organism files for endonuclease: {endo}")
|
| 64 |
+
|
| 65 |
+
# Get organism mappings from database manager
|
| 66 |
+
organisms_to_files, organisms_to_endos = self.settings.db_manager.get_organisms_and_endos()
|
| 67 |
+
|
| 68 |
+
# Process each organism that has this endonuclease
|
| 69 |
+
for organism, endos in organisms_to_endos.items():
|
| 70 |
+
if endo in endos:
|
| 71 |
+
cspr_file = os.path.join(self.settings.CSPR_DB, organisms_to_files[organism][endo][0])
|
| 72 |
+
db_file = os.path.join(self.settings.CSPR_DB, organisms_to_files[organism][endo][1])
|
| 73 |
+
|
| 74 |
+
if not os.path.exists(db_file):
|
| 75 |
+
self.logger.warning(f"Database file not found: {db_file}")
|
| 76 |
+
continue
|
| 77 |
+
|
| 78 |
+
org_files.append((organism, cspr_file, db_file))
|
| 79 |
+
|
| 80 |
+
# Store the mapping for later use
|
| 81 |
+
index = len(org_files) - 1
|
| 82 |
+
self.index_to_cspr[index] = cspr_file
|
| 83 |
+
self.index_to_db[index] = db_file
|
| 84 |
+
|
| 85 |
+
self.logger.info(f"Found {len(org_files)} organism files")
|
| 86 |
except Exception as e:
|
| 87 |
+
self.logger.error(f"Error getting organism files: {str(e)}")
|
| 88 |
+
show_error(self.settings, "Error getting organism files", str(e))
|
| 89 |
return org_files
|
| 90 |
|
| 91 |
def get_shared_seeds(self, db_files, limit=False):
|
|
|
|
| 170 |
locations = []
|
| 171 |
try:
|
| 172 |
for db_file in db_files:
|
| 173 |
+
# Get organism name from CSPR file
|
| 174 |
+
cspr_file = db_file.replace("_repeats.db", ".cspr")
|
| 175 |
+
with open(cspr_file, 'r') as f:
|
| 176 |
+
organism_name = f.readline().split(":")[-1].strip()
|
| 177 |
+
|
| 178 |
with sqlite3.connect(db_file) as conn:
|
| 179 |
c = conn.cursor()
|
| 180 |
for seed in seeds:
|
|
|
|
| 189 |
locations.append({
|
| 190 |
'seed': seed,
|
| 191 |
'sequence': sequence,
|
| 192 |
+
'organism': organism_name,
|
| 193 |
'chromosome': chrom,
|
| 194 |
'location': abs(int(locs[i]))
|
| 195 |
})
|
| 196 |
+
self.logger.debug(f"Found {len(locations)} locations")
|
| 197 |
except Exception as e:
|
| 198 |
+
self.logger.error(f"Error getting seed locations: {str(e)}")
|
| 199 |
show_error(self.global_settings, "Error getting seed locations", str(e))
|
| 200 |
return locations
|
| 201 |
|
| 202 |
def get_org_names(self):
|
| 203 |
+
try:
|
| 204 |
+
self.org_names = {}
|
| 205 |
+
for i, cspr_file in self.index_to_cspr.items():
|
| 206 |
+
with open(cspr_file, 'r') as f:
|
| 207 |
+
org_name = f.readline().split(":")[-1].strip()
|
| 208 |
+
self.org_names[i] = org_name
|
| 209 |
+
self.logger.info(f"Loaded {len(self.org_names)} organism names")
|
| 210 |
+
except Exception as e:
|
| 211 |
+
self.logger.error(f"Error getting organism names: {str(e)}")
|
| 212 |
+
show_error(self.settings, "Error getting organism names", str(e))
|
| 213 |
|
|
@@ -4,13 +4,23 @@ from models.AnnotationParser import AnnotationParser
|
|
| 4 |
import os
|
| 5 |
from Bio import SeqIO
|
| 6 |
from Bio.Seq import Seq
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
class ViewTargetsModel(HomeWindowModel):
|
| 9 |
def __init__(self, global_settings):
|
| 10 |
super().__init__(global_settings)
|
| 11 |
self.targets = []
|
| 12 |
self.cspr_parser = None
|
| 13 |
-
self.annotation_parser =
|
| 14 |
self.gene_sequence = ""
|
| 15 |
self.highlighted_sequence = ""
|
| 16 |
self.gene_info = {}
|
|
@@ -22,175 +32,286 @@ class ViewTargetsModel(HomeWindowModel):
|
|
| 22 |
self.current_gene_end = 0
|
| 23 |
self.extended_sequence = ""
|
| 24 |
self.chromosome = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
def load_targets(self, selected_targets, organism, endonuclease):
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
self.targets = []
|
| 34 |
-
self.available_genes =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
for target in selected_targets:
|
| 36 |
-
self.chromosome = target['chromosome'] # Store the chromosome
|
| 37 |
chrom = target['chromosome']
|
| 38 |
start, end = map(int, target['location'].split('-'))
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
if annotation_files:
|
| 51 |
-
self.annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_files[0])
|
| 52 |
-
self.annotation_parser.set_annotation_file(self.annotation_path)
|
| 53 |
-
gene_data = self.annotation_parser.get_gene_data(gene_name)
|
| 54 |
-
print("gene_data", gene_data)
|
| 55 |
-
if gene_data:
|
| 56 |
-
self.gene_sequence = gene_data.get('sequence', '')
|
| 57 |
-
self.gene_info = gene_data.get('info', {})
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
start, end, strand = self._parse_location(location)
|
| 62 |
-
print("strand", strand)
|
| 63 |
-
self.current_gene_start = start + 1
|
| 64 |
-
self.current_gene_end = end
|
| 65 |
-
|
| 66 |
-
# Fetch the extended sequence
|
| 67 |
-
extended_start = max(0, start - 30)
|
| 68 |
-
extended_end = end + 30
|
| 69 |
-
extended_seq = self.get_sequence_from_annotation(self.chromosome, extended_start, extended_end)
|
| 70 |
-
|
| 71 |
-
if strand == '-':
|
| 72 |
-
# Reverse complement the entire extended sequence
|
| 73 |
-
self.gene_sequence = str(Seq(self.gene_sequence).reverse_complement())
|
| 74 |
-
print("self.gene_sequence", self.gene_sequence)
|
| 75 |
-
|
| 76 |
-
# Create the extended sequence with lowercase placeholders
|
| 77 |
-
left_placeholder = extended_seq[:30].lower()
|
| 78 |
-
right_placeholder = extended_seq[-30:].lower()
|
| 79 |
-
self.gene_sequence = extended_seq[30:-30]
|
| 80 |
-
self.extended_sequence = f"{left_placeholder}{self.gene_sequence}{right_placeholder}"
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
-
|
| 87 |
-
parts = location.split(':')
|
| 88 |
-
start = parts[0]
|
| 89 |
-
end_strand = parts[1]
|
| 90 |
-
end = end_strand[:-3] # Remove the last 3 characters (strand info)
|
| 91 |
-
strand = end_strand[-2:-1] # Get the strand info (last 2 characters, excluding the closing parenthesis)
|
| 92 |
-
return int(start), int(end), strand
|
| 93 |
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
|
| 101 |
-
def
|
| 102 |
-
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
-
def
|
| 106 |
-
|
| 107 |
-
if
|
| 108 |
-
self.
|
| 109 |
-
self.
|
| 110 |
-
|
| 111 |
-
else:
|
| 112 |
-
self.global_settings.logger.warning("No annotation files found.")
|
| 113 |
-
self.available_genes = []
|
| 114 |
|
| 115 |
def get_gene_data(self, gene_name):
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
return self.gene_sequence
|
| 141 |
|
| 142 |
-
def
|
| 143 |
-
return self.
|
| 144 |
|
| 145 |
def highlight_targets_in_gene_viewer(self, selected_targets):
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
if index != -1:
|
| 154 |
-
highlighted_sequence = (
|
| 155 |
-
highlighted_sequence[:index] +
|
| 156 |
-
f"<span style='background-color: green;'>{highlighted_sequence[index:index+len(sequence)]}</span>" +
|
| 157 |
-
highlighted_sequence[index+len(sequence):]
|
| 158 |
-
)
|
| 159 |
-
else: # strand == '-'
|
| 160 |
-
rev_comp_sequence = str(Seq(sequence).reverse_complement())
|
| 161 |
-
index = highlighted_sequence.upper().find(rev_comp_sequence.upper())
|
| 162 |
-
if index != -1:
|
| 163 |
-
highlighted_sequence = (
|
| 164 |
-
highlighted_sequence[:index] +
|
| 165 |
-
f"<span style='background-color: red;'>{highlighted_sequence[index:index+len(rev_comp_sequence)]}</span>" +
|
| 166 |
-
highlighted_sequence[index+len(rev_comp_sequence):]
|
| 167 |
-
)
|
| 168 |
-
|
| 169 |
-
self.highlighted_sequence = highlighted_sequence
|
| 170 |
-
return self.highlighted_sequence
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
|
| 175 |
-
|
| 176 |
-
|
|
|
|
| 177 |
|
| 178 |
-
|
| 179 |
-
return self.scoring_options
|
| 180 |
|
| 181 |
-
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
|
| 188 |
-
|
| 189 |
-
# Implement export logic here
|
| 190 |
-
pass
|
| 191 |
|
| 192 |
-
def
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
import os
|
| 5 |
from Bio import SeqIO
|
| 6 |
from Bio.Seq import Seq
|
| 7 |
+
from functools import lru_cache
|
| 8 |
+
import threading
|
| 9 |
+
from collections import defaultdict
|
| 10 |
+
import re
|
| 11 |
+
import traceback
|
| 12 |
+
import time
|
| 13 |
+
import logging
|
| 14 |
+
from multiprocessing import Pool
|
| 15 |
+
from functools import partial
|
| 16 |
+
from multiprocessing import cpu_count
|
| 17 |
|
| 18 |
class ViewTargetsModel(HomeWindowModel):
|
| 19 |
def __init__(self, global_settings):
|
| 20 |
super().__init__(global_settings)
|
| 21 |
self.targets = []
|
| 22 |
self.cspr_parser = None
|
| 23 |
+
self.annotation_parser = None # Will be initialized when needed
|
| 24 |
self.gene_sequence = ""
|
| 25 |
self.highlighted_sequence = ""
|
| 26 |
self.gene_info = {}
|
|
|
|
| 32 |
self.current_gene_end = 0
|
| 33 |
self.extended_sequence = ""
|
| 34 |
self.chromosome = ""
|
| 35 |
+
|
| 36 |
+
# Cache structures
|
| 37 |
+
self._gene_data_cache = {}
|
| 38 |
+
self._sequence_cache = {}
|
| 39 |
+
self._parser_cache = {}
|
| 40 |
+
self._chromosome_seqs = {}
|
| 41 |
+
self._cached_targets = {} # Add cache for targets
|
| 42 |
+
|
| 43 |
+
def cleanup(self):
|
| 44 |
+
"""Cleanup method to be called when the view is closed"""
|
| 45 |
+
try:
|
| 46 |
+
# Disconnect from annotation file changes
|
| 47 |
+
if hasattr(self, '_annotation_signal'):
|
| 48 |
+
self.global_settings.annotation_file_changed.disconnect(self._on_annotation_file_changed)
|
| 49 |
+
self.global_settings.logger.debug("ViewTargetsModel disconnected from annotation file changes")
|
| 50 |
+
|
| 51 |
+
# Clear caches
|
| 52 |
+
self._gene_data_cache.clear()
|
| 53 |
+
self._sequence_cache.clear()
|
| 54 |
+
self._parser_cache.clear()
|
| 55 |
+
|
| 56 |
+
except Exception as e:
|
| 57 |
+
self.global_settings.logger.error(f"Error in ViewTargetsModel cleanup: {str(e)}")
|
| 58 |
+
|
| 59 |
+
def _on_annotation_file_changed(self, new_annotation_file):
|
| 60 |
+
"""Clear all caches when annotation file changes"""
|
| 61 |
+
try:
|
| 62 |
+
self.global_settings.logger.debug(f"ViewTargetsModel clearing caches for new annotation file: {new_annotation_file}")
|
| 63 |
+
self._gene_data_cache.clear()
|
| 64 |
+
self._sequence_cache.clear()
|
| 65 |
+
self._parser_cache.clear()
|
| 66 |
+
|
| 67 |
+
# Update annotation path and parser
|
| 68 |
+
self.annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', new_annotation_file)
|
| 69 |
+
self.annotation_parser = AnnotationParser(self.global_settings)
|
| 70 |
+
self.annotation_parser.set_annotation_file(self.annotation_path)
|
| 71 |
+
|
| 72 |
+
# Clear other stored data
|
| 73 |
+
self.gene_sequence = ""
|
| 74 |
+
self.highlighted_sequence = ""
|
| 75 |
+
self.gene_info = {}
|
| 76 |
+
self.available_genes = []
|
| 77 |
+
self._chromosome_seqs = {}
|
| 78 |
+
|
| 79 |
+
except Exception as e:
|
| 80 |
+
self.global_settings.logger.error(f"Error in _on_annotation_file_changed: {str(e)}")
|
| 81 |
|
| 82 |
def load_targets(self, selected_targets, organism, endonuclease):
|
| 83 |
+
"""Fast target loading with minimal file operations"""
|
| 84 |
+
start_time = time.time()
|
| 85 |
+
|
| 86 |
+
try:
|
| 87 |
+
self.global_settings.logger.debug(f"Starting load_targets with {len(selected_targets)} targets")
|
| 88 |
+
|
| 89 |
+
# Store organism and endonuclease for potential reloading
|
| 90 |
+
self.organism = organism
|
| 91 |
+
self.endonuclease = endonuclease
|
| 92 |
|
| 93 |
+
# Get CSPR parser from cache or create new one
|
| 94 |
+
parser_start = time.time()
|
| 95 |
+
cspr_key = f"{organism}_{endonuclease}"
|
| 96 |
+
if cspr_key in self._parser_cache:
|
| 97 |
+
self.cspr_parser = self._parser_cache[cspr_key]
|
| 98 |
+
else:
|
| 99 |
+
org_files = self.get_organism_to_files()
|
| 100 |
+
if organism not in org_files or endonuclease not in org_files[organism]:
|
| 101 |
+
self.global_settings.logger.error(f"No CSPR file found for {organism} and {endonuclease}")
|
| 102 |
+
return
|
| 103 |
+
|
| 104 |
+
cspr_file = org_files[organism][endonuclease][0]
|
| 105 |
+
cspr_path = os.path.join(self.global_settings.get_db_path(), cspr_file)
|
| 106 |
+
self.cspr_parser = CSPRparser(cspr_path, self.global_settings.get_casper_info_path())
|
| 107 |
+
self._parser_cache[cspr_key] = self.cspr_parser
|
| 108 |
+
parser_time = time.time() - parser_start
|
| 109 |
+
|
| 110 |
+
# Initialize targets and genes
|
| 111 |
self.targets = []
|
| 112 |
+
self.available_genes = set()
|
| 113 |
+
|
| 114 |
+
# Set up annotation parser if needed
|
| 115 |
+
if self.annotation_parser is None:
|
| 116 |
+
annotation_start = time.time()
|
| 117 |
+
self.annotation_parser = AnnotationParser(self.global_settings)
|
| 118 |
+
annotation_files = self.get_annotation_files()
|
| 119 |
+
if annotation_files:
|
| 120 |
+
self.annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_files[0])
|
| 121 |
+
self.annotation_parser.set_annotation_file(self.annotation_path)
|
| 122 |
+
annotation_time = time.time() - annotation_start
|
| 123 |
+
else:
|
| 124 |
+
annotation_time = 0
|
| 125 |
+
|
| 126 |
+
# Process targets in batches by chromosome
|
| 127 |
+
processing_start = time.time()
|
| 128 |
+
|
| 129 |
+
# Group targets by chromosome and prepare batch reading
|
| 130 |
+
batch_targets = defaultdict(list)
|
| 131 |
for target in selected_targets:
|
|
|
|
| 132 |
chrom = target['chromosome']
|
| 133 |
start, end = map(int, target['location'].split('-'))
|
| 134 |
+
batch_targets[chrom].append({
|
| 135 |
+
'feature_name': target['feature_name'],
|
| 136 |
+
'start': start,
|
| 137 |
+
'end': end
|
| 138 |
+
})
|
| 139 |
+
self.available_genes.add(target['feature_name'])
|
| 140 |
+
|
| 141 |
+
# Batch process targets for each chromosome
|
| 142 |
+
target_count = 0
|
| 143 |
+
for chrom, targets in batch_targets.items():
|
| 144 |
+
self.chromosome = chrom
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
+
# Sort targets by start position for more efficient reading
|
| 147 |
+
targets.sort(key=lambda x: x['start'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
|
| 149 |
+
# Read targets in a single batch per chromosome
|
| 150 |
+
batch_results = self.cspr_parser.read_targets_batch(
|
| 151 |
+
chromosome=chrom,
|
| 152 |
+
targets=targets,
|
| 153 |
+
endonuclease=endonuclease
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
if batch_results:
|
| 157 |
+
self.targets.extend(batch_results)
|
| 158 |
+
target_count += len(batch_results)
|
| 159 |
|
| 160 |
+
processing_time = time.time() - processing_start
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
|
| 162 |
+
# Convert genes to sorted list
|
| 163 |
+
self.available_genes = sorted(list(self.available_genes))
|
| 164 |
+
|
| 165 |
+
total_time = time.time() - start_time
|
| 166 |
+
self.global_settings.logger.debug(f"Total load_targets execution time: {total_time:.2f} seconds")
|
| 167 |
+
self.global_settings.logger.debug(f"Found {target_count} total CSPR targets")
|
| 168 |
|
| 169 |
+
except Exception as e:
|
| 170 |
+
self.global_settings.logger.error(f"Error in load_targets: {str(e)}\n{traceback.format_exc()}")
|
| 171 |
+
raise
|
| 172 |
|
| 173 |
+
def _get_chromosome_sequence(self, chromosome):
|
| 174 |
+
"""Get chromosome sequence on demand"""
|
| 175 |
+
if not hasattr(self, '_chromosome_seqs'):
|
| 176 |
+
self._chromosome_seqs = {}
|
| 177 |
+
|
| 178 |
+
if chromosome not in self._chromosome_seqs:
|
| 179 |
+
for record in SeqIO.parse(self.annotation_path, "genbank"):
|
| 180 |
+
if record.id == chromosome:
|
| 181 |
+
self._chromosome_seqs[chromosome] = str(record.seq)
|
| 182 |
+
break
|
| 183 |
+
|
| 184 |
+
return self._chromosome_seqs.get(chromosome)
|
| 185 |
|
| 186 |
+
def _initialize_annotation_parser(self):
|
| 187 |
+
"""Initialize annotation parser if not already initialized"""
|
| 188 |
+
if self.annotation_parser is None:
|
| 189 |
+
self.annotation_parser = AnnotationParser(self.global_settings)
|
| 190 |
+
if self.annotation_path:
|
| 191 |
+
self.annotation_parser.set_annotation_file(self.annotation_path)
|
|
|
|
|
|
|
|
|
|
| 192 |
|
| 193 |
def get_gene_data(self, gene_name):
|
| 194 |
+
"""Get gene data with caching"""
|
| 195 |
+
try:
|
| 196 |
+
if not gene_name:
|
| 197 |
+
self.global_settings.logger.error("No gene name provided")
|
| 198 |
+
return None
|
| 199 |
+
|
| 200 |
+
# Check model cache first
|
| 201 |
+
if gene_name in self._gene_data_cache:
|
| 202 |
+
return self._gene_data_cache[gene_name]
|
| 203 |
+
|
| 204 |
+
# Make sure annotation parser is initialized
|
| 205 |
+
if self.annotation_parser is None:
|
| 206 |
+
self._initialize_annotation_parser()
|
| 207 |
+
|
| 208 |
+
# Get gene data from parser
|
| 209 |
+
gene_data = self.annotation_parser.get_gene_data(gene_name)
|
| 210 |
+
if gene_data:
|
| 211 |
+
self._gene_data_cache[gene_name] = gene_data
|
| 212 |
+
|
| 213 |
+
return gene_data
|
| 214 |
+
|
| 215 |
+
except Exception as e:
|
| 216 |
+
self.global_settings.logger.error(f"Error getting gene data: {str(e)}")
|
| 217 |
+
return None
|
|
|
|
| 218 |
|
| 219 |
+
def get_targets(self):
|
| 220 |
+
return self.targets
|
| 221 |
|
| 222 |
def highlight_targets_in_gene_viewer(self, selected_targets):
|
| 223 |
+
"""Highlight selected targets in gene viewer"""
|
| 224 |
+
try:
|
| 225 |
+
self.global_settings.logger.debug("Starting highlight_targets_in_gene_viewer")
|
| 226 |
+
sequence = self.extended_sequence
|
| 227 |
+
if not sequence:
|
| 228 |
+
self.global_settings.logger.error("No extended sequence available")
|
| 229 |
+
return sequence
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
|
| 231 |
+
self.global_settings.logger.debug(f"Extended sequence length: {len(sequence)}")
|
| 232 |
+
|
| 233 |
+
# Sort targets by position for efficient highlighting
|
| 234 |
+
highlights = []
|
| 235 |
+
for target in selected_targets:
|
| 236 |
+
self.global_settings.logger.debug(f"Processing target: {target}")
|
| 237 |
+
sequence_to_find = target['sequence']
|
| 238 |
+
strand = target['strand']
|
| 239 |
+
|
| 240 |
+
# For negative strand, we need to use reverse complement
|
| 241 |
+
if strand == '-':
|
| 242 |
+
sequence_to_find = str(Seq(sequence_to_find).reverse_complement())
|
| 243 |
+
self.global_settings.logger.debug(f"Reverse complemented sequence: {sequence_to_find}")
|
| 244 |
+
|
| 245 |
+
# Search for the sequence in the gene viewer text
|
| 246 |
+
sequence_upper = sequence.upper()
|
| 247 |
+
target_upper = sequence_to_find.upper()
|
| 248 |
+
|
| 249 |
+
self.global_settings.logger.debug(f"Searching for sequence: {target_upper}")
|
| 250 |
+
|
| 251 |
+
# Find all occurrences
|
| 252 |
+
pos = sequence_upper.find(target_upper)
|
| 253 |
+
if pos != -1:
|
| 254 |
+
self.global_settings.logger.debug(f"Found sequence at position: {pos}")
|
| 255 |
+
color = 'red' if strand == '-' else 'green'
|
| 256 |
+
highlights.append((pos, len(sequence_to_find), color))
|
| 257 |
+
else:
|
| 258 |
+
self.global_settings.logger.warning(f"Sequence not found: {target_upper}")
|
| 259 |
|
| 260 |
+
if not highlights:
|
| 261 |
+
self.global_settings.logger.error("No sequences could be highlighted")
|
| 262 |
+
return sequence
|
| 263 |
|
| 264 |
+
self.global_settings.logger.debug(f"Found {len(highlights)} sequences to highlight")
|
|
|
|
| 265 |
|
| 266 |
+
# Build highlighted sequence
|
| 267 |
+
result = []
|
| 268 |
+
last_pos = 0
|
| 269 |
+
for pos, length, color in highlights:
|
| 270 |
+
result.append(sequence[last_pos:pos])
|
| 271 |
+
result.append(f"<span style='background-color: {color};'>")
|
| 272 |
+
result.append(sequence[pos:pos+length])
|
| 273 |
+
result.append("</span>")
|
| 274 |
+
last_pos = pos + length
|
| 275 |
+
|
| 276 |
+
result.append(sequence[last_pos:])
|
| 277 |
+
final_sequence = ''.join(result)
|
| 278 |
+
|
| 279 |
+
self.global_settings.logger.debug(f"Final highlighted sequence length: {len(final_sequence)}")
|
| 280 |
+
return final_sequence
|
| 281 |
|
| 282 |
+
except Exception as e:
|
| 283 |
+
self.global_settings.logger.error(f"Error highlighting targets: {str(e)}\n{traceback.format_exc()}")
|
| 284 |
+
return sequence
|
| 285 |
+
|
| 286 |
+
def get_available_genes(self):
|
| 287 |
+
"""Get list of available genes from the loaded targets"""
|
| 288 |
+
try:
|
| 289 |
+
# Return the available genes list that was populated during load_targets
|
| 290 |
+
if hasattr(self, 'available_genes'):
|
| 291 |
+
return self.available_genes
|
| 292 |
+
|
| 293 |
+
# If not already populated, get unique genes from targets
|
| 294 |
+
genes = set()
|
| 295 |
+
for target in self.targets:
|
| 296 |
+
if 'feature_name' in target:
|
| 297 |
+
genes.add(target['feature_name'])
|
| 298 |
+
|
| 299 |
+
# Store for future use
|
| 300 |
+
self.available_genes = sorted(list(genes))
|
| 301 |
+
return self.available_genes
|
| 302 |
+
|
| 303 |
+
except Exception as e:
|
| 304 |
+
self.global_settings.logger.error(f"Error getting available genes: {str(e)}")
|
| 305 |
+
return []
|
| 306 |
|
| 307 |
+
# ... (other methods remain unchanged)
|
|
|
|
|
|
|
| 308 |
|
| 309 |
+
def _process_target(self, target):
|
| 310 |
+
"""Process a single target - moved to separate method for parallel processing"""
|
| 311 |
+
try:
|
| 312 |
+
# Your existing target processing logic here
|
| 313 |
+
# Make sure to handle any shared resources thread-safely
|
| 314 |
+
pass
|
| 315 |
+
except Exception as e:
|
| 316 |
+
logging.error(f"Error processing target: {e}")
|
| 317 |
+
return None
|
|
@@ -19,11 +19,19 @@
|
|
| 19 |
<string>MainWindow</string>
|
| 20 |
</property>
|
| 21 |
<widget class="QWidget" name="centralwidget">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
-
<item row="
|
| 24 |
<layout class="QGridLayout" name="gridLayout">
|
| 25 |
-
<item row="
|
| 26 |
-
<widget class="QGroupBox" name="
|
| 27 |
<property name="sizePolicy">
|
| 28 |
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
| 29 |
<horstretch>0</horstretch>
|
|
@@ -34,8 +42,8 @@
|
|
| 34 |
<string>Select Organism and Endonuclease:</string>
|
| 35 |
</property>
|
| 36 |
<layout class="QGridLayout" name="gridLayout_8">
|
| 37 |
-
<item row="
|
| 38 |
-
<widget class="QComboBox" name="
|
| 39 |
<property name="sizePolicy">
|
| 40 |
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 41 |
<horstretch>0</horstretch>
|
|
@@ -50,10 +58,10 @@
|
|
| 50 |
</property>
|
| 51 |
</widget>
|
| 52 |
</item>
|
| 53 |
-
<item row="
|
| 54 |
<layout class="QHBoxLayout" name="horizontalLayout">
|
| 55 |
<item>
|
| 56 |
-
<widget class="QPushButton" name="
|
| 57 |
<property name="sizePolicy">
|
| 58 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 59 |
<horstretch>0</horstretch>
|
|
@@ -72,7 +80,7 @@
|
|
| 72 |
</widget>
|
| 73 |
</item>
|
| 74 |
<item>
|
| 75 |
-
<widget class="QToolButton" name="
|
| 76 |
<property name="minimumSize">
|
| 77 |
<size>
|
| 78 |
<width>0</width>
|
|
@@ -86,8 +94,8 @@
|
|
| 86 |
</item>
|
| 87 |
</layout>
|
| 88 |
</item>
|
| 89 |
-
<item row="
|
| 90 |
-
<widget class="QLabel" name="
|
| 91 |
<property name="sizePolicy">
|
| 92 |
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 93 |
<horstretch>0</horstretch>
|
|
@@ -99,8 +107,8 @@
|
|
| 99 |
</property>
|
| 100 |
</widget>
|
| 101 |
</item>
|
| 102 |
-
<item row="
|
| 103 |
-
<widget class="QTableWidget" name="
|
| 104 |
<property name="minimumSize">
|
| 105 |
<size>
|
| 106 |
<width>400</width>
|
|
@@ -109,8 +117,8 @@
|
|
| 109 |
</property>
|
| 110 |
</widget>
|
| 111 |
</item>
|
| 112 |
-
<item row="
|
| 113 |
-
<widget class="QLabel" name="
|
| 114 |
<property name="sizePolicy">
|
| 115 |
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 116 |
<horstretch>0</horstretch>
|
|
@@ -122,8 +130,8 @@
|
|
| 122 |
</property>
|
| 123 |
</widget>
|
| 124 |
</item>
|
| 125 |
-
<item row="
|
| 126 |
-
<widget class="QComboBox" name="
|
| 127 |
<property name="sizePolicy">
|
| 128 |
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 129 |
<horstretch>0</horstretch>
|
|
@@ -138,40 +146,8 @@
|
|
| 138 |
</property>
|
| 139 |
</widget>
|
| 140 |
</item>
|
| 141 |
-
<item row="
|
| 142 |
-
<
|
| 143 |
-
<property name="orientation">
|
| 144 |
-
<enum>Qt::Vertical</enum>
|
| 145 |
-
</property>
|
| 146 |
-
<property name="sizeType">
|
| 147 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 148 |
-
</property>
|
| 149 |
-
<property name="sizeHint" stdset="0">
|
| 150 |
-
<size>
|
| 151 |
-
<width>10</width>
|
| 152 |
-
<height>10</height>
|
| 153 |
-
</size>
|
| 154 |
-
</property>
|
| 155 |
-
</spacer>
|
| 156 |
-
</item>
|
| 157 |
-
<item row="6" column="0" colspan="2">
|
| 158 |
-
<spacer name="verticalSpacer_4">
|
| 159 |
-
<property name="orientation">
|
| 160 |
-
<enum>Qt::Vertical</enum>
|
| 161 |
-
</property>
|
| 162 |
-
<property name="sizeType">
|
| 163 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 164 |
-
</property>
|
| 165 |
-
<property name="sizeHint" stdset="0">
|
| 166 |
-
<size>
|
| 167 |
-
<width>10</width>
|
| 168 |
-
<height>10</height>
|
| 169 |
-
</size>
|
| 170 |
-
</property>
|
| 171 |
-
</spacer>
|
| 172 |
-
</item>
|
| 173 |
-
<item row="4" column="0">
|
| 174 |
-
<widget class="QCheckBox" name="selectAll">
|
| 175 |
<property name="text">
|
| 176 |
<string>Select All</string>
|
| 177 |
</property>
|
|
@@ -180,8 +156,8 @@
|
|
| 180 |
</layout>
|
| 181 |
</widget>
|
| 182 |
</item>
|
| 183 |
-
<item row="
|
| 184 |
-
<widget class="QGroupBox" name="
|
| 185 |
<property name="sizePolicy">
|
| 186 |
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
| 187 |
<horstretch>0</horstretch>
|
|
@@ -198,40 +174,8 @@
|
|
| 198 |
<string>Seed Analysis</string>
|
| 199 |
</property>
|
| 200 |
<layout class="QGridLayout" name="gridLayout_6">
|
| 201 |
-
<item row="3" column="0">
|
| 202 |
-
<spacer name="verticalSpacer_8">
|
| 203 |
-
<property name="orientation">
|
| 204 |
-
<enum>Qt::Vertical</enum>
|
| 205 |
-
</property>
|
| 206 |
-
<property name="sizeType">
|
| 207 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 208 |
-
</property>
|
| 209 |
-
<property name="sizeHint" stdset="0">
|
| 210 |
-
<size>
|
| 211 |
-
<width>10</width>
|
| 212 |
-
<height>10</height>
|
| 213 |
-
</size>
|
| 214 |
-
</property>
|
| 215 |
-
</spacer>
|
| 216 |
-
</item>
|
| 217 |
<item row="0" column="0">
|
| 218 |
-
<
|
| 219 |
-
<property name="orientation">
|
| 220 |
-
<enum>Qt::Vertical</enum>
|
| 221 |
-
</property>
|
| 222 |
-
<property name="sizeType">
|
| 223 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 224 |
-
</property>
|
| 225 |
-
<property name="sizeHint" stdset="0">
|
| 226 |
-
<size>
|
| 227 |
-
<width>10</width>
|
| 228 |
-
<height>10</height>
|
| 229 |
-
</size>
|
| 230 |
-
</property>
|
| 231 |
-
</spacer>
|
| 232 |
-
</item>
|
| 233 |
-
<item row="1" column="0">
|
| 234 |
-
<widget class="QTabWidget" name="tabWidget">
|
| 235 |
<property name="sizePolicy">
|
| 236 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 237 |
<horstretch>0</horstretch>
|
|
@@ -247,13 +191,13 @@
|
|
| 247 |
<property name="currentIndex">
|
| 248 |
<number>0</number>
|
| 249 |
</property>
|
| 250 |
-
<widget class="QWidget" name="
|
| 251 |
<attribute name="title">
|
| 252 |
<string>Scaffold/Chromosome Viewer:</string>
|
| 253 |
</attribute>
|
| 254 |
<layout class="QGridLayout" name="gridLayout_4">
|
| 255 |
<item row="0" column="0">
|
| 256 |
-
<widget class="QGraphicsView" name="
|
| 257 |
<property name="sizePolicy">
|
| 258 |
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 259 |
<horstretch>0</horstretch>
|
|
@@ -269,7 +213,7 @@
|
|
| 269 |
</widget>
|
| 270 |
</item>
|
| 271 |
<item row="1" column="0">
|
| 272 |
-
<widget class="QScrollArea" name="
|
| 273 |
<property name="sizePolicy">
|
| 274 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 275 |
<horstretch>0</horstretch>
|
|
@@ -279,13 +223,13 @@
|
|
| 279 |
<property name="widgetResizable">
|
| 280 |
<bool>true</bool>
|
| 281 |
</property>
|
| 282 |
-
<widget class="QWidget" name="
|
| 283 |
<property name="geometry">
|
| 284 |
<rect>
|
| 285 |
<x>0</x>
|
| 286 |
<y>0</y>
|
| 287 |
-
<width>
|
| 288 |
-
<height>
|
| 289 |
</rect>
|
| 290 |
</property>
|
| 291 |
</widget>
|
|
@@ -293,13 +237,13 @@
|
|
| 293 |
</item>
|
| 294 |
</layout>
|
| 295 |
</widget>
|
| 296 |
-
<widget class="QWidget" name="
|
| 297 |
<attribute name="title">
|
| 298 |
<string>Seed Distribution</string>
|
| 299 |
</attribute>
|
| 300 |
<layout class="QGridLayout" name="gridLayout_9">
|
| 301 |
<item row="0" column="0">
|
| 302 |
-
<widget class="QWidget" name="
|
| 303 |
</item>
|
| 304 |
</layout>
|
| 305 |
</widget>
|
|
@@ -308,8 +252,8 @@
|
|
| 308 |
</layout>
|
| 309 |
</widget>
|
| 310 |
</item>
|
| 311 |
-
<item row="
|
| 312 |
-
<widget class="QGroupBox" name="
|
| 313 |
<property name="sizePolicy">
|
| 314 |
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
| 315 |
<horstretch>0</horstretch>
|
|
@@ -332,24 +276,8 @@
|
|
| 332 |
<string>Global Analysis</string>
|
| 333 |
</property>
|
| 334 |
<layout class="QGridLayout" name="gridLayout_7">
|
| 335 |
-
<item row="
|
| 336 |
-
<
|
| 337 |
-
<property name="orientation">
|
| 338 |
-
<enum>Qt::Vertical</enum>
|
| 339 |
-
</property>
|
| 340 |
-
<property name="sizeType">
|
| 341 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 342 |
-
</property>
|
| 343 |
-
<property name="sizeHint" stdset="0">
|
| 344 |
-
<size>
|
| 345 |
-
<width>10</width>
|
| 346 |
-
<height>10</height>
|
| 347 |
-
</size>
|
| 348 |
-
</property>
|
| 349 |
-
</spacer>
|
| 350 |
-
</item>
|
| 351 |
-
<item row="1" column="0">
|
| 352 |
-
<widget class="QPushButton" name="statistics_overview">
|
| 353 |
<property name="sizePolicy">
|
| 354 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 355 |
<horstretch>0</horstretch>
|
|
@@ -367,8 +295,8 @@
|
|
| 367 |
</property>
|
| 368 |
</widget>
|
| 369 |
</item>
|
| 370 |
-
<item row="
|
| 371 |
-
<widget class="QTabWidget" name="
|
| 372 |
<property name="sizePolicy">
|
| 373 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 374 |
<horstretch>0</horstretch>
|
|
@@ -378,23 +306,23 @@
|
|
| 378 |
<property name="currentIndex">
|
| 379 |
<number>1</number>
|
| 380 |
</property>
|
| 381 |
-
<widget class="QWidget" name="
|
| 382 |
<attribute name="title">
|
| 383 |
-
<string>Repeats per ID Number</string>
|
| 384 |
</attribute>
|
| 385 |
<layout class="QGridLayout" name="gridLayout_11">
|
| 386 |
<item row="0" column="0">
|
| 387 |
-
<widget class="QWidget" name="
|
| 388 |
</item>
|
| 389 |
</layout>
|
| 390 |
</widget>
|
| 391 |
-
<widget class="QWidget" name="
|
| 392 |
<attribute name="title">
|
| 393 |
-
<string>
|
| 394 |
</attribute>
|
| 395 |
<layout class="QGridLayout" name="gridLayout_10">
|
| 396 |
<item row="0" column="0">
|
| 397 |
-
<widget class="QWidget" name="
|
| 398 |
<property name="sizePolicy">
|
| 399 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 400 |
<horstretch>0</horstretch>
|
|
@@ -407,41 +335,18 @@
|
|
| 407 |
</widget>
|
| 408 |
</widget>
|
| 409 |
</item>
|
| 410 |
-
<item row="0" column="0">
|
| 411 |
-
<spacer name="verticalSpacer_5">
|
| 412 |
-
<property name="orientation">
|
| 413 |
-
<enum>Qt::Vertical</enum>
|
| 414 |
-
</property>
|
| 415 |
-
<property name="sizeType">
|
| 416 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 417 |
-
</property>
|
| 418 |
-
<property name="sizeHint" stdset="0">
|
| 419 |
-
<size>
|
| 420 |
-
<width>10</width>
|
| 421 |
-
<height>10</height>
|
| 422 |
-
</size>
|
| 423 |
-
</property>
|
| 424 |
-
</spacer>
|
| 425 |
-
</item>
|
| 426 |
</layout>
|
| 427 |
</widget>
|
| 428 |
</item>
|
| 429 |
<item row="0" column="0">
|
| 430 |
-
<widget class="QLabel" name="
|
| 431 |
<property name="text">
|
| 432 |
<string>Multitargeting</string>
|
| 433 |
</property>
|
| 434 |
</widget>
|
| 435 |
</item>
|
| 436 |
-
<item row="
|
| 437 |
-
<widget class="
|
| 438 |
-
<property name="orientation">
|
| 439 |
-
<enum>Qt::Horizontal</enum>
|
| 440 |
-
</property>
|
| 441 |
-
</widget>
|
| 442 |
-
</item>
|
| 443 |
-
<item row="4" column="0" alignment="Qt::AlignLeft">
|
| 444 |
-
<widget class="QPushButton" name="back_button">
|
| 445 |
<property name="sizePolicy">
|
| 446 |
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 447 |
<horstretch>0</horstretch>
|
|
@@ -459,8 +364,8 @@
|
|
| 459 |
</property>
|
| 460 |
</widget>
|
| 461 |
</item>
|
| 462 |
-
<item row="
|
| 463 |
-
<widget class="QPushButton" name="
|
| 464 |
<property name="sizePolicy">
|
| 465 |
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 466 |
<horstretch>0</horstretch>
|
|
@@ -480,73 +385,8 @@
|
|
| 480 |
</item>
|
| 481 |
</layout>
|
| 482 |
</item>
|
| 483 |
-
<item row="1" column="0">
|
| 484 |
-
<spacer name="horizontalSpacer">
|
| 485 |
-
<property name="orientation">
|
| 486 |
-
<enum>Qt::Horizontal</enum>
|
| 487 |
-
</property>
|
| 488 |
-
<property name="sizeType">
|
| 489 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 490 |
-
</property>
|
| 491 |
-
<property name="sizeHint" stdset="0">
|
| 492 |
-
<size>
|
| 493 |
-
<width>20</width>
|
| 494 |
-
<height>20</height>
|
| 495 |
-
</size>
|
| 496 |
-
</property>
|
| 497 |
-
</spacer>
|
| 498 |
-
</item>
|
| 499 |
-
<item row="2" column="1">
|
| 500 |
-
<spacer name="verticalSpacer">
|
| 501 |
-
<property name="orientation">
|
| 502 |
-
<enum>Qt::Vertical</enum>
|
| 503 |
-
</property>
|
| 504 |
-
<property name="sizeType">
|
| 505 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 506 |
-
</property>
|
| 507 |
-
<property name="sizeHint" stdset="0">
|
| 508 |
-
<size>
|
| 509 |
-
<width>20</width>
|
| 510 |
-
<height>20</height>
|
| 511 |
-
</size>
|
| 512 |
-
</property>
|
| 513 |
-
</spacer>
|
| 514 |
-
</item>
|
| 515 |
-
<item row="1" column="2">
|
| 516 |
-
<spacer name="horizontalSpacer_2">
|
| 517 |
-
<property name="orientation">
|
| 518 |
-
<enum>Qt::Horizontal</enum>
|
| 519 |
-
</property>
|
| 520 |
-
<property name="sizeType">
|
| 521 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 522 |
-
</property>
|
| 523 |
-
<property name="sizeHint" stdset="0">
|
| 524 |
-
<size>
|
| 525 |
-
<width>20</width>
|
| 526 |
-
<height>20</height>
|
| 527 |
-
</size>
|
| 528 |
-
</property>
|
| 529 |
-
</spacer>
|
| 530 |
-
</item>
|
| 531 |
-
<item row="0" column="1">
|
| 532 |
-
<spacer name="verticalSpacer_2">
|
| 533 |
-
<property name="orientation">
|
| 534 |
-
<enum>Qt::Vertical</enum>
|
| 535 |
-
</property>
|
| 536 |
-
<property name="sizeType">
|
| 537 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 538 |
-
</property>
|
| 539 |
-
<property name="sizeHint" stdset="0">
|
| 540 |
-
<size>
|
| 541 |
-
<width>20</width>
|
| 542 |
-
<height>20</height>
|
| 543 |
-
</size>
|
| 544 |
-
</property>
|
| 545 |
-
</spacer>
|
| 546 |
-
</item>
|
| 547 |
</layout>
|
| 548 |
</widget>
|
| 549 |
-
<widget class="QStatusBar" name="statusbar"/>
|
| 550 |
</widget>
|
| 551 |
<resources/>
|
| 552 |
<connections/>
|
|
|
|
| 19 |
<string>MainWindow</string>
|
| 20 |
</property>
|
| 21 |
<widget class="QWidget" name="centralwidget">
|
| 22 |
+
<property name="geometry">
|
| 23 |
+
<rect>
|
| 24 |
+
<x>0</x>
|
| 25 |
+
<y>0</y>
|
| 26 |
+
<width>885</width>
|
| 27 |
+
<height>564</height>
|
| 28 |
+
</rect>
|
| 29 |
+
</property>
|
| 30 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 31 |
+
<item row="0" column="0">
|
| 32 |
<layout class="QGridLayout" name="gridLayout">
|
| 33 |
+
<item row="1" column="0" rowspan="2">
|
| 34 |
+
<widget class="QGroupBox" name="grpSelectOrganism">
|
| 35 |
<property name="sizePolicy">
|
| 36 |
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
| 37 |
<horstretch>0</horstretch>
|
|
|
|
| 42 |
<string>Select Organism and Endonuclease:</string>
|
| 43 |
</property>
|
| 44 |
<layout class="QGridLayout" name="gridLayout_8">
|
| 45 |
+
<item row="1" column="0">
|
| 46 |
+
<widget class="QComboBox" name="cmbOrganism">
|
| 47 |
<property name="sizePolicy">
|
| 48 |
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 49 |
<horstretch>0</horstretch>
|
|
|
|
| 58 |
</property>
|
| 59 |
</widget>
|
| 60 |
</item>
|
| 61 |
+
<item row="2" column="0" colspan="2">
|
| 62 |
<layout class="QHBoxLayout" name="horizontalLayout">
|
| 63 |
<item>
|
| 64 |
+
<widget class="QPushButton" name="pbtnAnalyze">
|
| 65 |
<property name="sizePolicy">
|
| 66 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 67 |
<horstretch>0</horstretch>
|
|
|
|
| 80 |
</widget>
|
| 81 |
</item>
|
| 82 |
<item>
|
| 83 |
+
<widget class="QToolButton" name="tbtnSQLSettings">
|
| 84 |
<property name="minimumSize">
|
| 85 |
<size>
|
| 86 |
<width>0</width>
|
|
|
|
| 94 |
</item>
|
| 95 |
</layout>
|
| 96 |
</item>
|
| 97 |
+
<item row="0" column="1">
|
| 98 |
+
<widget class="QLabel" name="lblEndonuclease">
|
| 99 |
<property name="sizePolicy">
|
| 100 |
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 101 |
<horstretch>0</horstretch>
|
|
|
|
| 107 |
</property>
|
| 108 |
</widget>
|
| 109 |
</item>
|
| 110 |
+
<item row="4" column="0" colspan="2">
|
| 111 |
+
<widget class="QTableWidget" name="tblSeeds">
|
| 112 |
<property name="minimumSize">
|
| 113 |
<size>
|
| 114 |
<width>400</width>
|
|
|
|
| 117 |
</property>
|
| 118 |
</widget>
|
| 119 |
</item>
|
| 120 |
+
<item row="0" column="0">
|
| 121 |
+
<widget class="QLabel" name="lblOrganism">
|
| 122 |
<property name="sizePolicy">
|
| 123 |
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 124 |
<horstretch>0</horstretch>
|
|
|
|
| 130 |
</property>
|
| 131 |
</widget>
|
| 132 |
</item>
|
| 133 |
+
<item row="1" column="1">
|
| 134 |
+
<widget class="QComboBox" name="cmbEndonuclease">
|
| 135 |
<property name="sizePolicy">
|
| 136 |
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 137 |
<horstretch>0</horstretch>
|
|
|
|
| 146 |
</property>
|
| 147 |
</widget>
|
| 148 |
</item>
|
| 149 |
+
<item row="3" column="0">
|
| 150 |
+
<widget class="QCheckBox" name="chkSelectAll">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
<property name="text">
|
| 152 |
<string>Select All</string>
|
| 153 |
</property>
|
|
|
|
| 156 |
</layout>
|
| 157 |
</widget>
|
| 158 |
</item>
|
| 159 |
+
<item row="1" column="1">
|
| 160 |
+
<widget class="QGroupBox" name="grpSeedAnalysis">
|
| 161 |
<property name="sizePolicy">
|
| 162 |
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
| 163 |
<horstretch>0</horstretch>
|
|
|
|
| 174 |
<string>Seed Analysis</string>
|
| 175 |
</property>
|
| 176 |
<layout class="QGridLayout" name="gridLayout_6">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
<item row="0" column="0">
|
| 178 |
+
<widget class="QTabWidget" name="tabsSeedAnalysis">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
<property name="sizePolicy">
|
| 180 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 181 |
<horstretch>0</horstretch>
|
|
|
|
| 191 |
<property name="currentIndex">
|
| 192 |
<number>0</number>
|
| 193 |
</property>
|
| 194 |
+
<widget class="QWidget" name="tabChromosomeViewer">
|
| 195 |
<attribute name="title">
|
| 196 |
<string>Scaffold/Chromosome Viewer:</string>
|
| 197 |
</attribute>
|
| 198 |
<layout class="QGridLayout" name="gridLayout_4">
|
| 199 |
<item row="0" column="0">
|
| 200 |
+
<widget class="QGraphicsView" name="graphviewChromosome">
|
| 201 |
<property name="sizePolicy">
|
| 202 |
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 203 |
<horstretch>0</horstretch>
|
|
|
|
| 213 |
</widget>
|
| 214 |
</item>
|
| 215 |
<item row="1" column="0">
|
| 216 |
+
<widget class="QScrollArea" name="scrollChromosome">
|
| 217 |
<property name="sizePolicy">
|
| 218 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 219 |
<horstretch>0</horstretch>
|
|
|
|
| 223 |
<property name="widgetResizable">
|
| 224 |
<bool>true</bool>
|
| 225 |
</property>
|
| 226 |
+
<widget class="QWidget" name="scrollviewChromosome">
|
| 227 |
<property name="geometry">
|
| 228 |
<rect>
|
| 229 |
<x>0</x>
|
| 230 |
<y>0</y>
|
| 231 |
+
<width>369</width>
|
| 232 |
+
<height>112</height>
|
| 233 |
</rect>
|
| 234 |
</property>
|
| 235 |
</widget>
|
|
|
|
| 237 |
</item>
|
| 238 |
</layout>
|
| 239 |
</widget>
|
| 240 |
+
<widget class="QWidget" name="tabSeedDistribution">
|
| 241 |
<attribute name="title">
|
| 242 |
<string>Seed Distribution</string>
|
| 243 |
</attribute>
|
| 244 |
<layout class="QGridLayout" name="gridLayout_9">
|
| 245 |
<item row="0" column="0">
|
| 246 |
+
<widget class="QWidget" name="plotRepeatVsChromosome" native="true"/>
|
| 247 |
</item>
|
| 248 |
</layout>
|
| 249 |
</widget>
|
|
|
|
| 252 |
</layout>
|
| 253 |
</widget>
|
| 254 |
</item>
|
| 255 |
+
<item row="2" column="1">
|
| 256 |
+
<widget class="QGroupBox" name="grpGlobalAnalysis">
|
| 257 |
<property name="sizePolicy">
|
| 258 |
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
| 259 |
<horstretch>0</horstretch>
|
|
|
|
| 276 |
<string>Global Analysis</string>
|
| 277 |
</property>
|
| 278 |
<layout class="QGridLayout" name="gridLayout_7">
|
| 279 |
+
<item row="0" column="0">
|
| 280 |
+
<widget class="QPushButton" name="pbtnStatisticsOverview">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
<property name="sizePolicy">
|
| 282 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 283 |
<horstretch>0</horstretch>
|
|
|
|
| 295 |
</property>
|
| 296 |
</widget>
|
| 297 |
</item>
|
| 298 |
+
<item row="1" column="0">
|
| 299 |
+
<widget class="QTabWidget" name="tabsGlobalAnalysis">
|
| 300 |
<property name="sizePolicy">
|
| 301 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 302 |
<horstretch>0</horstretch>
|
|
|
|
| 306 |
<property name="currentIndex">
|
| 307 |
<number>1</number>
|
| 308 |
</property>
|
| 309 |
+
<widget class="QWidget" name="tabRepeatsVsSeed">
|
| 310 |
<attribute name="title">
|
| 311 |
+
<string>Repeats per Seed ID Number</string>
|
| 312 |
</attribute>
|
| 313 |
<layout class="QGridLayout" name="gridLayout_11">
|
| 314 |
<item row="0" column="0">
|
| 315 |
+
<widget class="QWidget" name="plotRepeatsVsSeed" native="true"/>
|
| 316 |
</item>
|
| 317 |
</layout>
|
| 318 |
</widget>
|
| 319 |
+
<widget class="QWidget" name="tabSequencesVsRepeats">
|
| 320 |
<attribute name="title">
|
| 321 |
+
<string>Sequences per Number Repeats</string>
|
| 322 |
</attribute>
|
| 323 |
<layout class="QGridLayout" name="gridLayout_10">
|
| 324 |
<item row="0" column="0">
|
| 325 |
+
<widget class="QWidget" name="plotSequencesVsRepeats" native="true">
|
| 326 |
<property name="sizePolicy">
|
| 327 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 328 |
<horstretch>0</horstretch>
|
|
|
|
| 335 |
</widget>
|
| 336 |
</widget>
|
| 337 |
</item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
</layout>
|
| 339 |
</widget>
|
| 340 |
</item>
|
| 341 |
<item row="0" column="0">
|
| 342 |
+
<widget class="QLabel" name="lblMultitargeting">
|
| 343 |
<property name="text">
|
| 344 |
<string>Multitargeting</string>
|
| 345 |
</property>
|
| 346 |
</widget>
|
| 347 |
</item>
|
| 348 |
+
<item row="3" column="0" alignment="Qt::AlignLeft">
|
| 349 |
+
<widget class="QPushButton" name="pbtnBank">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
<property name="sizePolicy">
|
| 351 |
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 352 |
<horstretch>0</horstretch>
|
|
|
|
| 364 |
</property>
|
| 365 |
</widget>
|
| 366 |
</item>
|
| 367 |
+
<item row="3" column="1" alignment="Qt::AlignRight">
|
| 368 |
+
<widget class="QPushButton" name="pbtnExport">
|
| 369 |
<property name="sizePolicy">
|
| 370 |
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 371 |
<horstretch>0</horstretch>
|
|
|
|
| 385 |
</item>
|
| 386 |
</layout>
|
| 387 |
</item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
</layout>
|
| 389 |
</widget>
|
|
|
|
| 390 |
</widget>
|
| 391 |
<resources/>
|
| 392 |
<connections/>
|
|
@@ -21,16 +21,6 @@
|
|
| 21 |
<widget class="QWidget" name="centralwidget">
|
| 22 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
<item row="0" column="0">
|
| 24 |
-
<widget class="QLabel" name="lblRequired">
|
| 25 |
-
<property name="text">
|
| 26 |
-
<string><html><head/><body><p><span style=" color:#ff0000;">* Required</span></p></body></html></string>
|
| 27 |
-
</property>
|
| 28 |
-
<property name="alignment">
|
| 29 |
-
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
| 30 |
-
</property>
|
| 31 |
-
</widget>
|
| 32 |
-
</item>
|
| 33 |
-
<item row="1" column="0">
|
| 34 |
<layout class="QGridLayout" name="gridLayout">
|
| 35 |
<item row="2" column="0" colspan="2">
|
| 36 |
<widget class="QGroupBox" name="grpStep2">
|
|
|
|
| 21 |
<widget class="QWidget" name="centralwidget">
|
| 22 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
<item row="0" column="0">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
<layout class="QGridLayout" name="gridLayout">
|
| 25 |
<item row="2" column="0" colspan="2">
|
| 26 |
<widget class="QGroupBox" name="grpStep2">
|
|
@@ -20,88 +20,27 @@
|
|
| 20 |
</property>
|
| 21 |
<widget class="QWidget" name="centralwidget">
|
| 22 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
-
<item row="0" column="
|
| 24 |
-
<spacer name="verticalSpacer">
|
| 25 |
-
<property name="orientation">
|
| 26 |
-
<enum>Qt::Vertical</enum>
|
| 27 |
-
</property>
|
| 28 |
-
<property name="sizeType">
|
| 29 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 30 |
-
</property>
|
| 31 |
-
<property name="sizeHint" stdset="0">
|
| 32 |
-
<size>
|
| 33 |
-
<width>20</width>
|
| 34 |
-
<height>20</height>
|
| 35 |
-
</size>
|
| 36 |
-
</property>
|
| 37 |
-
</spacer>
|
| 38 |
-
</item>
|
| 39 |
-
<item row="1" column="1">
|
| 40 |
<layout class="QGridLayout" name="gridLayout">
|
| 41 |
-
<item row="
|
| 42 |
-
<widget class="QGroupBox" name="
|
| 43 |
<property name="sizePolicy">
|
| 44 |
-
<sizepolicy hsizetype="
|
| 45 |
<horstretch>0</horstretch>
|
| 46 |
<verstretch>0</verstretch>
|
| 47 |
</sizepolicy>
|
| 48 |
</property>
|
| 49 |
-
<property name="maximumSize">
|
| 50 |
-
<size>
|
| 51 |
-
<width>600</width>
|
| 52 |
-
<height>16777215</height>
|
| 53 |
-
</size>
|
| 54 |
-
</property>
|
| 55 |
<property name="toolTip">
|
| 56 |
-
<string
|
| 57 |
</property>
|
| 58 |
<property name="title">
|
| 59 |
-
<string>
|
| 60 |
</property>
|
| 61 |
-
<layout class="QGridLayout" name="
|
| 62 |
-
<item row="
|
| 63 |
-
<widget class="
|
| 64 |
-
<property name="font">
|
| 65 |
-
<font>
|
| 66 |
-
<weight>75</weight>
|
| 67 |
-
<bold>true</bold>
|
| 68 |
-
</font>
|
| 69 |
-
</property>
|
| 70 |
-
<property name="text">
|
| 71 |
-
<string>Analyze Organism(s)</string>
|
| 72 |
-
</property>
|
| 73 |
-
</widget>
|
| 74 |
-
</item>
|
| 75 |
-
<item row="0" column="0" colspan="2">
|
| 76 |
-
<spacer name="verticalSpacer_3">
|
| 77 |
-
<property name="orientation">
|
| 78 |
-
<enum>Qt::Vertical</enum>
|
| 79 |
-
</property>
|
| 80 |
-
<property name="sizeType">
|
| 81 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 82 |
-
</property>
|
| 83 |
-
<property name="sizeHint" stdset="0">
|
| 84 |
-
<size>
|
| 85 |
-
<width>10</width>
|
| 86 |
-
<height>10</height>
|
| 87 |
-
</size>
|
| 88 |
-
</property>
|
| 89 |
-
</spacer>
|
| 90 |
-
</item>
|
| 91 |
-
<item row="1" column="1">
|
| 92 |
-
<widget class="QComboBox" name="endoBox">
|
| 93 |
-
<property name="sizePolicy">
|
| 94 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 95 |
-
<horstretch>0</horstretch>
|
| 96 |
-
<verstretch>0</verstretch>
|
| 97 |
-
</sizepolicy>
|
| 98 |
-
</property>
|
| 99 |
-
</widget>
|
| 100 |
-
</item>
|
| 101 |
-
<item row="2" column="0" colspan="2">
|
| 102 |
-
<widget class="QTableWidget" name="org_Table">
|
| 103 |
<property name="sizePolicy">
|
| 104 |
-
<sizepolicy hsizetype="
|
| 105 |
<horstretch>0</horstretch>
|
| 106 |
<verstretch>0</verstretch>
|
| 107 |
</sizepolicy>
|
|
@@ -114,23 +53,47 @@
|
|
| 114 |
</property>
|
| 115 |
</widget>
|
| 116 |
</item>
|
| 117 |
-
<item row="
|
| 118 |
-
<widget class="QLabel" name="
|
| 119 |
<property name="sizePolicy">
|
| 120 |
-
<sizepolicy hsizetype="
|
| 121 |
<horstretch>0</horstretch>
|
| 122 |
<verstretch>0</verstretch>
|
| 123 |
</sizepolicy>
|
| 124 |
</property>
|
| 125 |
<property name="text">
|
| 126 |
-
<string>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
</property>
|
| 128 |
</widget>
|
| 129 |
</item>
|
| 130 |
-
<item row="
|
| 131 |
-
<widget class="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
<property name="sizePolicy">
|
| 133 |
-
<sizepolicy hsizetype="
|
| 134 |
<horstretch>0</horstretch>
|
| 135 |
<verstretch>0</verstretch>
|
| 136 |
</sizepolicy>
|
|
@@ -142,88 +105,84 @@
|
|
| 142 |
</size>
|
| 143 |
</property>
|
| 144 |
<property name="toolTip">
|
| 145 |
-
<string><html><head/><body><p>
|
| 146 |
-
</property>
|
| 147 |
-
<property name="currentIndex">
|
| 148 |
-
<number>0</number>
|
| 149 |
</property>
|
| 150 |
-
<widget class="QWidget" name="tab">
|
| 151 |
-
<attribute name="title">
|
| 152 |
-
<string>Shared Seed Heatmap</string>
|
| 153 |
-
</attribute>
|
| 154 |
-
<layout class="QGridLayout" name="gridLayout_3">
|
| 155 |
-
<item row="0" column="0">
|
| 156 |
-
<widget class="QWidget" name="colormap_figure" native="true"/>
|
| 157 |
-
</item>
|
| 158 |
-
</layout>
|
| 159 |
-
</widget>
|
| 160 |
</widget>
|
| 161 |
</item>
|
| 162 |
-
<item row="
|
| 163 |
-
<
|
| 164 |
-
<property name="
|
| 165 |
-
<
|
| 166 |
</property>
|
| 167 |
-
<property name="
|
| 168 |
-
<
|
| 169 |
</property>
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
|
|
|
| 175 |
</property>
|
| 176 |
-
</
|
| 177 |
</item>
|
| 178 |
</layout>
|
| 179 |
</widget>
|
| 180 |
</item>
|
| 181 |
-
<item row="
|
| 182 |
-
<widget class="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
<property name="minimumSize">
|
| 184 |
<size>
|
| 185 |
-
<width>
|
| 186 |
<height>0</height>
|
| 187 |
</size>
|
| 188 |
</property>
|
|
|
|
|
|
|
|
|
|
| 189 |
<property name="text">
|
| 190 |
-
<string>
|
| 191 |
</property>
|
| 192 |
</widget>
|
| 193 |
</item>
|
| 194 |
-
<item row="
|
| 195 |
-
<widget class="QGroupBox" name="
|
| 196 |
<property name="sizePolicy">
|
| 197 |
-
<sizepolicy hsizetype="
|
| 198 |
<horstretch>0</horstretch>
|
| 199 |
<verstretch>0</verstretch>
|
| 200 |
</sizepolicy>
|
| 201 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
<property name="toolTip">
|
| 203 |
-
<string
|
| 204 |
</property>
|
| 205 |
<property name="title">
|
| 206 |
-
<string>
|
| 207 |
</property>
|
| 208 |
-
<layout class="QGridLayout" name="
|
| 209 |
-
<item row="
|
| 210 |
-
<widget class="
|
| 211 |
-
<property name="text">
|
| 212 |
-
<string>Find Locations</string>
|
| 213 |
-
</property>
|
| 214 |
-
</widget>
|
| 215 |
-
</item>
|
| 216 |
-
<item row="2" column="0">
|
| 217 |
-
<widget class="QPushButton" name="query_seed_button">
|
| 218 |
-
<property name="text">
|
| 219 |
-
<string>Query Seed</string>
|
| 220 |
-
</property>
|
| 221 |
-
</widget>
|
| 222 |
-
</item>
|
| 223 |
-
<item row="3" column="0" colspan="3">
|
| 224 |
-
<widget class="QTableWidget" name="table2">
|
| 225 |
<property name="sizePolicy">
|
| 226 |
-
<sizepolicy hsizetype="
|
| 227 |
<horstretch>0</horstretch>
|
| 228 |
<verstretch>0</verstretch>
|
| 229 |
</sizepolicy>
|
|
@@ -235,57 +194,40 @@
|
|
| 235 |
</size>
|
| 236 |
</property>
|
| 237 |
<property name="toolTip">
|
| 238 |
-
<string><html><head/><body><p>
|
| 239 |
-
</property>
|
| 240 |
-
</widget>
|
| 241 |
-
</item>
|
| 242 |
-
<item row="2" column="2">
|
| 243 |
-
<widget class="QPushButton" name="clear_Button">
|
| 244 |
-
<property name="text">
|
| 245 |
-
<string>Clear Seeds</string>
|
| 246 |
-
</property>
|
| 247 |
-
</widget>
|
| 248 |
-
</item>
|
| 249 |
-
<item row="1" column="1" colspan="2">
|
| 250 |
-
<widget class="QLineEdit" name="seed_input"/>
|
| 251 |
-
</item>
|
| 252 |
-
<item row="0" column="0" colspan="3">
|
| 253 |
-
<spacer name="verticalSpacer_5">
|
| 254 |
-
<property name="orientation">
|
| 255 |
-
<enum>Qt::Vertical</enum>
|
| 256 |
-
</property>
|
| 257 |
-
<property name="sizeType">
|
| 258 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 259 |
-
</property>
|
| 260 |
-
<property name="sizeHint" stdset="0">
|
| 261 |
-
<size>
|
| 262 |
-
<width>10</width>
|
| 263 |
-
<height>10</height>
|
| 264 |
-
</size>
|
| 265 |
</property>
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
<item row="4" column="2">
|
| 269 |
-
<widget class="QPushButton" name="clear_loc_button">
|
| 270 |
-
<property name="text">
|
| 271 |
-
<string>Clear Locations</string>
|
| 272 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
</widget>
|
| 274 |
</item>
|
| 275 |
-
<item row="
|
| 276 |
-
<widget class="QLabel" name="
|
| 277 |
-
<property name="
|
| 278 |
-
<
|
|
|
|
|
|
|
|
|
|
| 279 |
</property>
|
| 280 |
<property name="text">
|
| 281 |
-
<string
|
| 282 |
</property>
|
| 283 |
</widget>
|
| 284 |
</item>
|
| 285 |
-
<item row="
|
| 286 |
-
<widget class="QTableWidget" name="
|
| 287 |
<property name="sizePolicy">
|
| 288 |
-
<sizepolicy hsizetype="
|
| 289 |
<horstretch>0</horstretch>
|
| 290 |
<verstretch>0</verstretch>
|
| 291 |
</sizepolicy>
|
|
@@ -298,127 +240,49 @@
|
|
| 298 |
</property>
|
| 299 |
</widget>
|
| 300 |
</item>
|
| 301 |
-
<item row="
|
| 302 |
-
<widget class="
|
| 303 |
-
<property name="
|
| 304 |
-
<
|
| 305 |
-
<
|
| 306 |
-
<
|
| 307 |
-
</
|
| 308 |
</property>
|
| 309 |
<property name="text">
|
| 310 |
-
<string>
|
| 311 |
</property>
|
| 312 |
</widget>
|
| 313 |
</item>
|
| 314 |
-
<item row="
|
| 315 |
-
<
|
| 316 |
-
<property name="
|
| 317 |
-
<
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
</property>
|
| 322 |
-
<property name="sizeHint" stdset="0">
|
| 323 |
-
<size>
|
| 324 |
-
<width>10</width>
|
| 325 |
-
<height>10</height>
|
| 326 |
-
</size>
|
| 327 |
</property>
|
| 328 |
-
</
|
| 329 |
</item>
|
| 330 |
</layout>
|
| 331 |
</widget>
|
| 332 |
</item>
|
| 333 |
-
<item row="
|
| 334 |
-
<widget class="QPushButton" name="
|
| 335 |
<property name="minimumSize">
|
| 336 |
<size>
|
| 337 |
-
<width>
|
| 338 |
<height>0</height>
|
| 339 |
</size>
|
| 340 |
</property>
|
| 341 |
-
<property name="toolTip">
|
| 342 |
-
<string><html><head/><body><p>Click to export selected gRNAs. <span style=" font-weight:600;">Note: </span>only rows from the first (top right) table can be selected and exported.</p></body></html></string>
|
| 343 |
-
</property>
|
| 344 |
-
<property name="text">
|
| 345 |
-
<string>Export Selected gRNAs</string>
|
| 346 |
-
</property>
|
| 347 |
-
</widget>
|
| 348 |
-
</item>
|
| 349 |
-
<item row="0" column="0" colspan="2">
|
| 350 |
-
<widget class="QLabel" name="title">
|
| 351 |
-
<property name="sizePolicy">
|
| 352 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 353 |
-
<horstretch>0</horstretch>
|
| 354 |
-
<verstretch>0</verstretch>
|
| 355 |
-
</sizepolicy>
|
| 356 |
-
</property>
|
| 357 |
<property name="text">
|
| 358 |
-
<string>
|
| 359 |
-
</property>
|
| 360 |
-
</widget>
|
| 361 |
-
</item>
|
| 362 |
-
<item row="1" column="0" colspan="2">
|
| 363 |
-
<widget class="Line" name="line">
|
| 364 |
-
<property name="orientation">
|
| 365 |
-
<enum>Qt::Horizontal</enum>
|
| 366 |
</property>
|
| 367 |
</widget>
|
| 368 |
</item>
|
| 369 |
</layout>
|
| 370 |
</item>
|
| 371 |
-
<item row="2" column="1">
|
| 372 |
-
<spacer name="verticalSpacer_2">
|
| 373 |
-
<property name="orientation">
|
| 374 |
-
<enum>Qt::Vertical</enum>
|
| 375 |
-
</property>
|
| 376 |
-
<property name="sizeType">
|
| 377 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 378 |
-
</property>
|
| 379 |
-
<property name="sizeHint" stdset="0">
|
| 380 |
-
<size>
|
| 381 |
-
<width>20</width>
|
| 382 |
-
<height>20</height>
|
| 383 |
-
</size>
|
| 384 |
-
</property>
|
| 385 |
-
</spacer>
|
| 386 |
-
</item>
|
| 387 |
-
<item row="1" column="2">
|
| 388 |
-
<spacer name="horizontalSpacer">
|
| 389 |
-
<property name="orientation">
|
| 390 |
-
<enum>Qt::Horizontal</enum>
|
| 391 |
-
</property>
|
| 392 |
-
<property name="sizeType">
|
| 393 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 394 |
-
</property>
|
| 395 |
-
<property name="sizeHint" stdset="0">
|
| 396 |
-
<size>
|
| 397 |
-
<width>20</width>
|
| 398 |
-
<height>20</height>
|
| 399 |
-
</size>
|
| 400 |
-
</property>
|
| 401 |
-
</spacer>
|
| 402 |
-
</item>
|
| 403 |
-
<item row="1" column="0">
|
| 404 |
-
<spacer name="horizontalSpacer_2">
|
| 405 |
-
<property name="orientation">
|
| 406 |
-
<enum>Qt::Horizontal</enum>
|
| 407 |
-
</property>
|
| 408 |
-
<property name="sizeType">
|
| 409 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 410 |
-
</property>
|
| 411 |
-
<property name="sizeHint" stdset="0">
|
| 412 |
-
<size>
|
| 413 |
-
<width>20</width>
|
| 414 |
-
<height>20</height>
|
| 415 |
-
</size>
|
| 416 |
-
</property>
|
| 417 |
-
</spacer>
|
| 418 |
-
</item>
|
| 419 |
</layout>
|
| 420 |
</widget>
|
| 421 |
-
<widget class="QStatusBar" name="statusbar"/>
|
| 422 |
</widget>
|
| 423 |
<resources/>
|
| 424 |
<connections/>
|
|
|
|
| 20 |
</property>
|
| 21 |
<widget class="QWidget" name="centralwidget">
|
| 22 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
+
<item row="0" column="0">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
<layout class="QGridLayout" name="gridLayout">
|
| 25 |
+
<item row="1" column="1" rowspan="2">
|
| 26 |
+
<widget class="QGroupBox" name="grpSeedAnalysis">
|
| 27 |
<property name="sizePolicy">
|
| 28 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
| 29 |
<horstretch>0</horstretch>
|
| 30 |
<verstretch>0</verstretch>
|
| 31 |
</sizepolicy>
|
| 32 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
<property name="toolTip">
|
| 34 |
+
<string><html><head/><body><p>These tables show the seeds that are conserved among <span style=" font-weight:600;">all </span>selected organisms.</p></body></html></string>
|
| 35 |
</property>
|
| 36 |
<property name="title">
|
| 37 |
+
<string>Seed Analysis:</string>
|
| 38 |
</property>
|
| 39 |
+
<layout class="QGridLayout" name="gridLayout_5">
|
| 40 |
+
<item row="4" column="0" colspan="3">
|
| 41 |
+
<widget class="QTableWidget" name="tblLocation">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
<property name="sizePolicy">
|
| 43 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
| 44 |
<horstretch>0</horstretch>
|
| 45 |
<verstretch>0</verstretch>
|
| 46 |
</sizepolicy>
|
|
|
|
| 53 |
</property>
|
| 54 |
</widget>
|
| 55 |
</item>
|
| 56 |
+
<item row="0" column="0">
|
| 57 |
+
<widget class="QLabel" name="lblSeedSearch">
|
| 58 |
<property name="sizePolicy">
|
| 59 |
+
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 60 |
<horstretch>0</horstretch>
|
| 61 |
<verstretch>0</verstretch>
|
| 62 |
</sizepolicy>
|
| 63 |
</property>
|
| 64 |
<property name="text">
|
| 65 |
+
<string>Seed Search:</string>
|
| 66 |
+
</property>
|
| 67 |
+
</widget>
|
| 68 |
+
</item>
|
| 69 |
+
<item row="1" column="0">
|
| 70 |
+
<widget class="QPushButton" name="pbtnQuerySeed">
|
| 71 |
+
<property name="text">
|
| 72 |
+
<string>Query Seed</string>
|
| 73 |
</property>
|
| 74 |
</widget>
|
| 75 |
</item>
|
| 76 |
+
<item row="3" column="2">
|
| 77 |
+
<widget class="QPushButton" name="pbtnClearLocations">
|
| 78 |
+
<property name="text">
|
| 79 |
+
<string>Clear Locations</string>
|
| 80 |
+
</property>
|
| 81 |
+
</widget>
|
| 82 |
+
</item>
|
| 83 |
+
<item row="0" column="1" colspan="2">
|
| 84 |
+
<widget class="QLineEdit" name="ledSeed"/>
|
| 85 |
+
</item>
|
| 86 |
+
<item row="1" column="2">
|
| 87 |
+
<widget class="QPushButton" name="pbtnClearSeeds">
|
| 88 |
+
<property name="text">
|
| 89 |
+
<string>Clear Seeds</string>
|
| 90 |
+
</property>
|
| 91 |
+
</widget>
|
| 92 |
+
</item>
|
| 93 |
+
<item row="2" column="0" colspan="3">
|
| 94 |
+
<widget class="QTableWidget" name="tblSeed">
|
| 95 |
<property name="sizePolicy">
|
| 96 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
| 97 |
<horstretch>0</horstretch>
|
| 98 |
<verstretch>0</verstretch>
|
| 99 |
</sizepolicy>
|
|
|
|
| 105 |
</size>
|
| 106 |
</property>
|
| 107 |
<property name="toolTip">
|
| 108 |
+
<string><html><head/><body><p><span style=" font-weight:600;">% Coverage</span>: percentage of analyzed organisms covered by the given seed sequence. (Ex. a coverage of 75% for an analysis of 4 organisms means that the seed appears in 3/4 of the organisms) </p><p><span style=" font-weight:600;">Total Repeats</span>: the total number of times the seed sequence is repeated across all organisms analyzed </p><p><span style=" font-weight:600;">Avg. Repeats/Scaffold</span>: the average number of times the seed sequence is repeated per scaffold/chromosome/contig (varies depending on how the organism’s FASTA file is arranged) </p><p><span style=" font-weight:600;">Consensus Sequence</span>: the full length guide RNA sequence that appears most commonly amongst all occurrences of the given seed. </p><p><span style=" font-weight:600;">% Consensus</span>: the percentage of all seed repeats that have the tail of the consensus sequence. </p><p><span style=" font-weight:600;">Score</span>: on-target score for the consensus sequence.</p><p><span style=" font-weight:600;">PAM</span>: the protospacer adjacent motif of the consensus sequence.</p><p><span style=" font-weight:600;">Strand</span>: the strand directionality (+ or -) of the consensus sequence.</p></body></html></string>
|
|
|
|
|
|
|
|
|
|
| 109 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
</widget>
|
| 111 |
</item>
|
| 112 |
+
<item row="3" column="0">
|
| 113 |
+
<widget class="QLabel" name="lblLocationFInder">
|
| 114 |
+
<property name="toolTip">
|
| 115 |
+
<string><html><head/><body><p>Highlight a seed and click &quot;Find Locations&quot; to view the exact location and organism of every repeat belonging to that seed.</p></body></html></string>
|
| 116 |
</property>
|
| 117 |
+
<property name="text">
|
| 118 |
+
<string><html><head/><body><p><span style=" font-weight:600;">Location Finder:</span></p></body></html></string>
|
| 119 |
</property>
|
| 120 |
+
</widget>
|
| 121 |
+
</item>
|
| 122 |
+
<item row="3" column="1">
|
| 123 |
+
<widget class="QPushButton" name="pbtnFindLocations">
|
| 124 |
+
<property name="text">
|
| 125 |
+
<string>Find Locations</string>
|
| 126 |
</property>
|
| 127 |
+
</widget>
|
| 128 |
</item>
|
| 129 |
</layout>
|
| 130 |
</widget>
|
| 131 |
</item>
|
| 132 |
+
<item row="0" column="0" colspan="2">
|
| 133 |
+
<widget class="QLabel" name="lblPopulationAnalysis">
|
| 134 |
+
<property name="sizePolicy">
|
| 135 |
+
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 136 |
+
<horstretch>0</horstretch>
|
| 137 |
+
<verstretch>0</verstretch>
|
| 138 |
+
</sizepolicy>
|
| 139 |
+
</property>
|
| 140 |
+
<property name="text">
|
| 141 |
+
<string>Population Analysis</string>
|
| 142 |
+
</property>
|
| 143 |
+
</widget>
|
| 144 |
+
</item>
|
| 145 |
+
<item row="3" column="1" alignment="Qt::AlignRight">
|
| 146 |
+
<widget class="QPushButton" name="pbtnExportgRNA">
|
| 147 |
<property name="minimumSize">
|
| 148 |
<size>
|
| 149 |
+
<width>175</width>
|
| 150 |
<height>0</height>
|
| 151 |
</size>
|
| 152 |
</property>
|
| 153 |
+
<property name="toolTip">
|
| 154 |
+
<string><html><head/><body><p>Click to export selected gRNAs. <span style=" font-weight:600;">Note: </span>only rows from the first (top right) table can be selected and exported.</p></body></html></string>
|
| 155 |
+
</property>
|
| 156 |
<property name="text">
|
| 157 |
+
<string>Export Selected gRNAs</string>
|
| 158 |
</property>
|
| 159 |
</widget>
|
| 160 |
</item>
|
| 161 |
+
<item row="1" column="0" rowspan="2">
|
| 162 |
+
<widget class="QGroupBox" name="grpSelectOrganisms">
|
| 163 |
<property name="sizePolicy">
|
| 164 |
+
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 165 |
<horstretch>0</horstretch>
|
| 166 |
<verstretch>0</verstretch>
|
| 167 |
</sizepolicy>
|
| 168 |
</property>
|
| 169 |
+
<property name="maximumSize">
|
| 170 |
+
<size>
|
| 171 |
+
<width>600</width>
|
| 172 |
+
<height>16777215</height>
|
| 173 |
+
</size>
|
| 174 |
+
</property>
|
| 175 |
<property name="toolTip">
|
| 176 |
+
<string>Please select organism(s) and an endonuclease, and click "Analyze Organism(s)" to populate the population analysis window.</string>
|
| 177 |
</property>
|
| 178 |
<property name="title">
|
| 179 |
+
<string>Select Organism(s) and Endonuclease:</string>
|
| 180 |
</property>
|
| 181 |
+
<layout class="QGridLayout" name="gridLayout_6">
|
| 182 |
+
<item row="3" column="0" colspan="2">
|
| 183 |
+
<widget class="QTabWidget" name="tabsSharedSeedHeatmap">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
<property name="sizePolicy">
|
| 185 |
+
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
| 186 |
<horstretch>0</horstretch>
|
| 187 |
<verstretch>0</verstretch>
|
| 188 |
</sizepolicy>
|
|
|
|
| 194 |
</size>
|
| 195 |
</property>
|
| 196 |
<property name="toolTip">
|
| 197 |
+
<string><html><head/><body><p>Heatmap of shared repeats between all selected organisms. Diagonals show number of self-contained repeats. Axis labels correspond to the rows of the organism selection table.</p></body></html></string>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
</property>
|
| 199 |
+
<property name="currentIndex">
|
| 200 |
+
<number>0</number>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
</property>
|
| 202 |
+
<widget class="QWidget" name="tabSharedSeedHeatmap">
|
| 203 |
+
<attribute name="title">
|
| 204 |
+
<string>Shared Seed Heatmap</string>
|
| 205 |
+
</attribute>
|
| 206 |
+
<layout class="QGridLayout" name="gridLayout_3">
|
| 207 |
+
<item row="0" column="0">
|
| 208 |
+
<widget class="QWidget" name="heatmapSeed" native="true"/>
|
| 209 |
+
</item>
|
| 210 |
+
</layout>
|
| 211 |
+
</widget>
|
| 212 |
</widget>
|
| 213 |
</item>
|
| 214 |
+
<item row="0" column="0">
|
| 215 |
+
<widget class="QLabel" name="lblEndonuclease">
|
| 216 |
+
<property name="sizePolicy">
|
| 217 |
+
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 218 |
+
<horstretch>0</horstretch>
|
| 219 |
+
<verstretch>0</verstretch>
|
| 220 |
+
</sizepolicy>
|
| 221 |
</property>
|
| 222 |
<property name="text">
|
| 223 |
+
<string>Endonuclease:</string>
|
| 224 |
</property>
|
| 225 |
</widget>
|
| 226 |
</item>
|
| 227 |
+
<item row="1" column="0" colspan="2">
|
| 228 |
+
<widget class="QTableWidget" name="tblOrganism">
|
| 229 |
<property name="sizePolicy">
|
| 230 |
+
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
| 231 |
<horstretch>0</horstretch>
|
| 232 |
<verstretch>0</verstretch>
|
| 233 |
</sizepolicy>
|
|
|
|
| 240 |
</property>
|
| 241 |
</widget>
|
| 242 |
</item>
|
| 243 |
+
<item row="2" column="0" colspan="2">
|
| 244 |
+
<widget class="QPushButton" name="pbtnAnalyzeOrganism">
|
| 245 |
+
<property name="font">
|
| 246 |
+
<font>
|
| 247 |
+
<weight>75</weight>
|
| 248 |
+
<bold>true</bold>
|
| 249 |
+
</font>
|
| 250 |
</property>
|
| 251 |
<property name="text">
|
| 252 |
+
<string>Analyze Organism(s)</string>
|
| 253 |
</property>
|
| 254 |
</widget>
|
| 255 |
</item>
|
| 256 |
+
<item row="0" column="1">
|
| 257 |
+
<widget class="QComboBox" name="cmbEndonuclease">
|
| 258 |
+
<property name="sizePolicy">
|
| 259 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 260 |
+
<horstretch>0</horstretch>
|
| 261 |
+
<verstretch>0</verstretch>
|
| 262 |
+
</sizepolicy>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
</property>
|
| 264 |
+
</widget>
|
| 265 |
</item>
|
| 266 |
</layout>
|
| 267 |
</widget>
|
| 268 |
</item>
|
| 269 |
+
<item row="3" column="0" alignment="Qt::AlignLeft">
|
| 270 |
+
<widget class="QPushButton" name="pbtnBack">
|
| 271 |
<property name="minimumSize">
|
| 272 |
<size>
|
| 273 |
+
<width>125</width>
|
| 274 |
<height>0</height>
|
| 275 |
</size>
|
| 276 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
<property name="text">
|
| 278 |
+
<string>Back</string>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
</property>
|
| 280 |
</widget>
|
| 281 |
</item>
|
| 282 |
</layout>
|
| 283 |
</item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
</layout>
|
| 285 |
</widget>
|
|
|
|
| 286 |
</widget>
|
| 287 |
<resources/>
|
| 288 |
<connections/>
|
|
@@ -35,13 +35,8 @@ def show_error(settings, message, e):
|
|
| 35 |
|
| 36 |
def scale_ui(window, base_width=1920, base_height=1080, font_size=12, header_font_size=30, custom_scale_width=None, custom_scale_height=None):
|
| 37 |
try:
|
| 38 |
-
window.repaint()
|
| 39 |
-
QtWidgets.QApplication.processEvents()
|
| 40 |
-
|
| 41 |
# Get the primary screen
|
| 42 |
screen = QtGui.QGuiApplication.primaryScreen()
|
| 43 |
-
|
| 44 |
-
# Get the geometry of the screen
|
| 45 |
screen_geometry = screen.geometry()
|
| 46 |
width = screen_geometry.width()
|
| 47 |
height = screen_geometry.height()
|
|
@@ -53,31 +48,25 @@ def scale_ui(window, base_width=1920, base_height=1080, font_size=12, header_fon
|
|
| 53 |
scaled_title_font_size = int(header_font_size * (width / base_width))
|
| 54 |
window.title.setStyleSheet(f"font: bold {scaled_title_font_size}pt 'Arial';")
|
| 55 |
|
| 56 |
-
|
|
|
|
|
|
|
| 57 |
|
|
|
|
|
|
|
| 58 |
currentWidth = window.size().width()
|
| 59 |
currentHeight = window.size().height()
|
| 60 |
|
| 61 |
-
# Window resize and center
|
| 62 |
-
scaledWidth = int((width * (custom_scale_width if custom_scale_width else 1150)) / base_width)
|
| 63 |
-
scaledHeight = int((height * (custom_scale_height if custom_scale_height else 650)) / base_height)
|
| 64 |
-
|
| 65 |
if scaledHeight < currentHeight:
|
| 66 |
scaledHeight = currentHeight
|
| 67 |
if scaledWidth < currentWidth:
|
| 68 |
scaledWidth = currentWidth
|
| 69 |
|
| 70 |
-
#
|
| 71 |
-
|
| 72 |
-
x = centerPoint.x() - (scaledWidth // 2)
|
| 73 |
-
y = centerPoint.y() - (scaledHeight // 2)
|
| 74 |
-
window.setGeometry(x, y, scaledWidth, scaledHeight)
|
| 75 |
-
|
| 76 |
-
window.repaint()
|
| 77 |
-
QtWidgets.QApplication.processEvents()
|
| 78 |
|
| 79 |
except Exception as e:
|
| 80 |
-
print(f"Error in scale_ui: {e}")
|
| 81 |
|
| 82 |
def center_ui(window):
|
| 83 |
try:
|
|
|
|
| 35 |
|
| 36 |
def scale_ui(window, base_width=1920, base_height=1080, font_size=12, header_font_size=30, custom_scale_width=None, custom_scale_height=None):
|
| 37 |
try:
|
|
|
|
|
|
|
|
|
|
| 38 |
# Get the primary screen
|
| 39 |
screen = QtGui.QGuiApplication.primaryScreen()
|
|
|
|
|
|
|
| 40 |
screen_geometry = screen.geometry()
|
| 41 |
width = screen_geometry.width()
|
| 42 |
height = screen_geometry.height()
|
|
|
|
| 48 |
scaled_title_font_size = int(header_font_size * (width / base_width))
|
| 49 |
window.title.setStyleSheet(f"font: bold {scaled_title_font_size}pt 'Arial';")
|
| 50 |
|
| 51 |
+
# Calculate sizes
|
| 52 |
+
scaledWidth = int((width * (custom_scale_width if custom_scale_width else 1150)) / base_width)
|
| 53 |
+
scaledHeight = int((height * (custom_scale_height if custom_scale_height else 650)) / base_height)
|
| 54 |
|
| 55 |
+
# Ensure minimum size
|
| 56 |
+
window.adjustSize()
|
| 57 |
currentWidth = window.size().width()
|
| 58 |
currentHeight = window.size().height()
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
if scaledHeight < currentHeight:
|
| 61 |
scaledHeight = currentHeight
|
| 62 |
if scaledWidth < currentWidth:
|
| 63 |
scaledWidth = currentWidth
|
| 64 |
|
| 65 |
+
# Resize in a single operation
|
| 66 |
+
window.resize(scaledWidth, scaledHeight)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
except Exception as e:
|
| 69 |
+
print(f"Error in scale_ui: {e}")
|
| 70 |
|
| 71 |
def center_ui(window):
|
| 72 |
try:
|
|
@@ -1,429 +0,0 @@
|
|
| 1 |
-
###############################################################################
|
| 2 |
-
# INPUTS: inputs are the annotation files to parse. Currently, only gbff is supported.
|
| 3 |
-
# OUTPUTS: the outputs are data structures that store the parsed data
|
| 4 |
-
################################################################################
|
| 5 |
-
|
| 6 |
-
from PyQt5 import QtWidgets
|
| 7 |
-
import gffutils
|
| 8 |
-
import models.GlobalSettings as GlobalSettings
|
| 9 |
-
import os
|
| 10 |
-
from Bio import SeqIO
|
| 11 |
-
import traceback
|
| 12 |
-
|
| 13 |
-
logger = GlobalSettings.logger
|
| 14 |
-
|
| 15 |
-
class Annotation_Parser:
|
| 16 |
-
def __init__(self):
|
| 17 |
-
try:
|
| 18 |
-
#variables to use
|
| 19 |
-
self.annotationFileName = "" #this is the variable that holds the filename itself
|
| 20 |
-
self.txtLocusTag = False
|
| 21 |
-
self.isGff = False
|
| 22 |
-
self.isTxt = False
|
| 23 |
-
self.max_chrom = 0
|
| 24 |
-
|
| 25 |
-
#dictionary used for finding the genes in a txt annotation file
|
| 26 |
-
#key: locus_tag
|
| 27 |
-
#value: List of lists
|
| 28 |
-
# essentially its all based on locus tag. So the key is the locus tag, and its data is:
|
| 29 |
-
# [genomic accession, int, start, end, +\-]
|
| 30 |
-
self.reg_dict = dict()
|
| 31 |
-
|
| 32 |
-
#parallel dictionary used for the txt annotaion file
|
| 33 |
-
#key: name + symbol (space in between each word)
|
| 34 |
-
#value: locus_tag (indexes dict)
|
| 35 |
-
self.para_dict = dict()
|
| 36 |
-
|
| 37 |
-
#list of tuples containing (chromosome/scaffold # {int}, Feature matching search criteria {SeqFeature Object})
|
| 38 |
-
self.results_list = list()
|
| 39 |
-
|
| 40 |
-
except Exception as e:
|
| 41 |
-
logger.critical("Error initializing Annotation_Parser class.")
|
| 42 |
-
logger.critical(e)
|
| 43 |
-
logger.critical(traceback.format_exc())
|
| 44 |
-
msgBox = QtWidgets.QMessageBox()
|
| 45 |
-
msgBox.setStyleSheet("font: " + str(GlobalSettings.mainWindow.fontSize) + "pt 'Arial'")
|
| 46 |
-
msgBox.setIcon(QtWidgets.QMessageBox.Icon.Critical)
|
| 47 |
-
msgBox.setWindowTitle("Fatal Error")
|
| 48 |
-
msgBox.setText("Fatal Error:\n"+str(e)+ "\n\nFor more information on this error, look at CASPER.log in the application folder.")
|
| 49 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Close)
|
| 50 |
-
msgBox.exec()
|
| 51 |
-
|
| 52 |
-
exit(-1)
|
| 53 |
-
|
| 54 |
-
### This function takes a list of lists and flattens it into a single list. Useful when dealing with a list of lists where the nested lists only have 1 entry.
|
| 55 |
-
def flatten_list(self,t):
|
| 56 |
-
return [item.lower() for sublist in t for item in sublist]
|
| 57 |
-
|
| 58 |
-
### This function finds how many chromosomes are within the selcted annotation file and returns the value
|
| 59 |
-
def get_max_chrom(self):
|
| 60 |
-
parser = SeqIO.parse(self.annotationFileName, 'genbank') # Initialize parser (iterator) for each query
|
| 61 |
-
for i, record in enumerate(parser):
|
| 62 |
-
max_chrom = i+1
|
| 63 |
-
return max_chrom
|
| 64 |
-
|
| 65 |
-
def get_sequence_info(self, query):
|
| 66 |
-
try:
|
| 67 |
-
self.results_list.clear()
|
| 68 |
-
parser = SeqIO.parse(self.annotationFileName, 'genbank') # Initialize parser (iterator) for each query
|
| 69 |
-
for j,record in enumerate(parser): # Each record corresponds to a chromosome/scaffold in the FNA/FASTA file
|
| 70 |
-
tmp = str(record.seq).find(query)
|
| 71 |
-
if tmp != -1: # If match is found
|
| 72 |
-
return (j+1,tmp+1,tmp+len(query)) # Chromosome number, start index, stop index
|
| 73 |
-
else:
|
| 74 |
-
tmp = str(record.seq.reverse_complement()).find(query) # Check the reverse complement now
|
| 75 |
-
if tmp != -1: # If match is found
|
| 76 |
-
return (j+1,tmp-len(query),tmp-1) # Chromosome number, start index, stop index
|
| 77 |
-
else:
|
| 78 |
-
continue
|
| 79 |
-
return False
|
| 80 |
-
|
| 81 |
-
except Exception as e:
|
| 82 |
-
logger.critical("Error in get_sequence_info() in annotation parser.")
|
| 83 |
-
logger.critical(e)
|
| 84 |
-
logger.critical(traceback.format_exc())
|
| 85 |
-
msgBox = QtWidgets.QMessageBox()
|
| 86 |
-
msgBox.setStyleSheet("font: " + str(GlobalSettings.mainWindow.fontSize) + "pt 'Arial'")
|
| 87 |
-
msgBox.setIcon(QtWidgets.QMessageBox.Icon.Critical)
|
| 88 |
-
msgBox.setWindowTitle("Fatal Error")
|
| 89 |
-
msgBox.setText("Fatal Error:\n"+str(e)+ "\n\nFor more information on this error, look at CASPER.log in the application folder.")
|
| 90 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Close)
|
| 91 |
-
msgBox.exec()
|
| 92 |
-
|
| 93 |
-
exit(-1)
|
| 94 |
-
|
| 95 |
-
### The workhorse function of AnnotationParser, this searches the annotation file for the user's search and returns features matching the description.
|
| 96 |
-
def genbank_search(self, queries, same_search):
|
| 97 |
-
index_number = 0
|
| 98 |
-
try:
|
| 99 |
-
if same_search: # If searching for the same thing, just return the results from last time
|
| 100 |
-
return self.results_list
|
| 101 |
-
else:
|
| 102 |
-
self.results_list.clear()
|
| 103 |
-
for i, query in enumerate(queries):
|
| 104 |
-
parser = SeqIO.parse(self.annotationFileName, 'genbank') # Initialize parser (iterator) for each query
|
| 105 |
-
for j,record in enumerate(parser): # Each record corresponds to a chromosome/scaffold in the FNA/FASTA file
|
| 106 |
-
if i == 0:
|
| 107 |
-
index_number += 1
|
| 108 |
-
for feature in record.features: # Each feature corresponds to a gene, tRNA, rep_origin, etc. in the given record (chromosome/scaffold)
|
| 109 |
-
if "translation" in feature.qualifiers:
|
| 110 |
-
if query.lower() in " ".join(self.flatten_list(feature.qualifiers.values())[:-1]) and feature.type != "source" and feature.type != "gene": # If search matches the feature's qualifiers somewhere, save it
|
| 111 |
-
self.results_list.append((j+1,feature))
|
| 112 |
-
else: # If search not in the feature's qualifiers, move to the next feature
|
| 113 |
-
continue
|
| 114 |
-
else:
|
| 115 |
-
if query.lower() in " ".join(self.flatten_list(feature.qualifiers.values())) and feature.type != "source" and feature.type != "gene": # If search matches the feature's qualifiers somewhere, save it
|
| 116 |
-
self.results_list.append((j+1,feature))
|
| 117 |
-
else: # If search not in the feature's qualifiers, move to the next feature
|
| 118 |
-
continue
|
| 119 |
-
self.max_chrom = index_number # Counts the number of chromosomes/scaffolds in the organism (only do this once, even if there are multiple queries)
|
| 120 |
-
else:
|
| 121 |
-
for feature in record.features:
|
| 122 |
-
if "translation" in feature.qualifiers:
|
| 123 |
-
if query.lower() in " ".join(self.flatten_list(feature.qualifiers.values())[:-1]) and feature.type != "source" and feature.type != "gene": # If search matches the feature's qualifiers somewhere, save it
|
| 124 |
-
self.results_list.append((j+1,feature))
|
| 125 |
-
else: # If search not in the feature's qualifiers, move to the next feature
|
| 126 |
-
continue
|
| 127 |
-
else:
|
| 128 |
-
if query.lower() in " ".join(self.flatten_list(feature.qualifiers.values())) and feature.type != "source" and feature.type != "gene": # If search matches the feature's qualifiers somewhere, save it
|
| 129 |
-
self.results_list.append((j+1,feature))
|
| 130 |
-
else: # If search not in the feature's qualifiers, move to the next feature
|
| 131 |
-
continue
|
| 132 |
-
return self.results_list
|
| 133 |
-
|
| 134 |
-
except Exception as e:
|
| 135 |
-
logger.critical("Error in genbank_search() in annotation parser.")
|
| 136 |
-
logger.critical(e)
|
| 137 |
-
logger.critical(traceback.format_exc())
|
| 138 |
-
msgBox = QtWidgets.QMessageBox()
|
| 139 |
-
msgBox.setStyleSheet("font: " + str(GlobalSettings.mainWindow.fontSize) + "pt 'Arial'")
|
| 140 |
-
msgBox.setIcon(QtWidgets.QMessageBox.Icon.Critical)
|
| 141 |
-
msgBox.setWindowTitle("Fatal Error")
|
| 142 |
-
msgBox.setText("Fatal Error:\n"+str(e)+ "\n\nFor more information on this error, look at CASPER.log in the application folder.")
|
| 143 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Close)
|
| 144 |
-
msgBox.exec()
|
| 145 |
-
|
| 146 |
-
exit(-1)
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
# This function parses gff files and stores them in a dictionary
|
| 152 |
-
# It also creates a parallel dictionary to use in searching
|
| 153 |
-
# Precondition: ONLY TO BE USED WITH GFF FILES
|
| 154 |
-
def gff_parse(self):
|
| 155 |
-
try:
|
| 156 |
-
self.reg_dict.clear()
|
| 157 |
-
self.para_dict.clear()
|
| 158 |
-
prevFirstIndex = ""
|
| 159 |
-
indexNumber = 1
|
| 160 |
-
fileStream = open(self.annotationFileName)
|
| 161 |
-
data_base_file_name = GlobalSettings.CSPR_DB + "/" + "gff_database.db"
|
| 162 |
-
|
| 163 |
-
# temp list will be the following each time it is put into the dictionary:
|
| 164 |
-
# [Sequence ID (genomic accession or scaffold), the index number itself, the feature type (cds, gene, mrna), the start(-1), end, and the strand]
|
| 165 |
-
tempList = list()
|
| 166 |
-
currentLocusTag = ""
|
| 167 |
-
para_dict_key_string = ""
|
| 168 |
-
|
| 169 |
-
# initialize the data base (this is what parses it for me)
|
| 170 |
-
print("Intializing the data base")
|
| 171 |
-
db = gffutils.create_db(self.annotationFileName, dbfn=data_base_file_name, force=True, keep_order=True,
|
| 172 |
-
merge_strategy='merge', sort_attribute_values=True)
|
| 173 |
-
print("Finished intializing")
|
| 174 |
-
|
| 175 |
-
# call the feature version of that data base now
|
| 176 |
-
db = gffutils.FeatureDB(data_base_file_name, keep_order=True)
|
| 177 |
-
|
| 178 |
-
# now we go through that data base and get the data we want
|
| 179 |
-
for feature in db.all_features(limit=None, strand=None, featuretype=None, order_by=None, reverse=False,
|
| 180 |
-
completely_within=False):
|
| 181 |
-
# if the genomic accession/scaffold/chromseome changes, update the indexNumber
|
| 182 |
-
if prevFirstIndex != feature.seqid and prevFirstIndex != "":
|
| 183 |
-
indexNumber += 1
|
| 184 |
-
# if we find a new gene, update the locus_tag/name
|
| 185 |
-
if feature.featuretype == "gene" or feature.featuretype == 'pseudogene':
|
| 186 |
-
|
| 187 |
-
# check and see if locus tag is in the attributes, go on the Name if locus_tag is not in there
|
| 188 |
-
if 'locus_tag' in feature.attributes:
|
| 189 |
-
currentLocusTag = feature.attributes['locus_tag'][0]
|
| 190 |
-
else:
|
| 191 |
-
currentLocusTag = feature.attributes["Name"][0]
|
| 192 |
-
|
| 193 |
-
# once the locus tag changes, append it to the para_dict
|
| 194 |
-
if para_dict_key_string != "":
|
| 195 |
-
if para_dict_key_string not in self.para_dict:
|
| 196 |
-
self.para_dict[para_dict_key_string] = list()
|
| 197 |
-
self.para_dict[para_dict_key_string].append(currentLocusTag)
|
| 198 |
-
else:
|
| 199 |
-
if currentLocusTag not in self.para_dict[para_dict_key_string]:
|
| 200 |
-
self.para_dict[para_dict_key_string].append(currentLocusTag)
|
| 201 |
-
para_dict_key_string = ""
|
| 202 |
-
|
| 203 |
-
tempList = [currentLocusTag, indexNumber, feature.featuretype, feature.start - 1, feature.end,
|
| 204 |
-
feature.strand]
|
| 205 |
-
|
| 206 |
-
# insert that locus tag/name into the dictionary
|
| 207 |
-
if currentLocusTag not in self.reg_dict:
|
| 208 |
-
self.reg_dict[currentLocusTag] = []
|
| 209 |
-
self.reg_dict[currentLocusTag].append(tempList)
|
| 210 |
-
elif currentLocusTag in self.reg_dict:
|
| 211 |
-
self.reg_dict[currentLocusTag].append(tempList)
|
| 212 |
-
|
| 213 |
-
# go through each of this child's children
|
| 214 |
-
for child in db.children(feature.id, level=None, featuretype=None, order_by=None, reverse=False,
|
| 215 |
-
limit=None, completely_within=False):
|
| 216 |
-
tempList = [currentLocusTag, indexNumber, child.featuretype, child.start - 1, child.end, child.strand]
|
| 217 |
-
|
| 218 |
-
# only insert it if it hasn't been inserted before
|
| 219 |
-
if tempList not in self.reg_dict[currentLocusTag]:
|
| 220 |
-
self.reg_dict[currentLocusTag].append(tempList)
|
| 221 |
-
|
| 222 |
-
# now go through the other ones which are not region
|
| 223 |
-
elif feature.featuretype != "region" and feature.featuretype != "telomere" and feature.featuretype != "origin_of_replication":
|
| 224 |
-
tempList = [currentLocusTag, indexNumber, feature.featuretype, feature.start - 1, feature.end,
|
| 225 |
-
feature.strand]
|
| 226 |
-
|
| 227 |
-
# only insert if it hasn't been inserted before
|
| 228 |
-
if tempList not in self.reg_dict[currentLocusTag]:
|
| 229 |
-
self.reg_dict[currentLocusTag].append(tempList)
|
| 230 |
-
|
| 231 |
-
# now same as above, go through the children again
|
| 232 |
-
for child in db.children(feature.id, level=None, featuretype=None, order_by=None, reverse=False,
|
| 233 |
-
limit=None, completely_within=False):
|
| 234 |
-
tempList = [currentLocusTag, indexNumber, child.featuretype, child.start - 1, child.end,
|
| 235 |
-
child.strand]
|
| 236 |
-
|
| 237 |
-
if tempList not in self.reg_dict[currentLocusTag]:
|
| 238 |
-
self.reg_dict[currentLocusTag].append(tempList)
|
| 239 |
-
|
| 240 |
-
# now we need to get the para_dict up and running
|
| 241 |
-
# get the stuff out of the product part
|
| 242 |
-
if 'product' in feature.attributes and feature.featuretype == "CDS":
|
| 243 |
-
if para_dict_key_string == "":
|
| 244 |
-
para_dict_key_string = feature.attributes['product'][0]
|
| 245 |
-
else:
|
| 246 |
-
para_dict_key_string = para_dict_key_string + ";" + feature.attributes['product'][0]
|
| 247 |
-
# get the stuff out of the Note part
|
| 248 |
-
if 'Note' in feature.attributes:
|
| 249 |
-
if para_dict_key_string == "":
|
| 250 |
-
para_dict_key_string = feature.attributes['Note'][0]
|
| 251 |
-
else:
|
| 252 |
-
para_dict_key_string = para_dict_key_string + ";" + feature.attributes['Note'][0]
|
| 253 |
-
|
| 254 |
-
prevFirstIndex = feature.seqid
|
| 255 |
-
self.max_chrom = indexNumber
|
| 256 |
-
except Exception as e:
|
| 257 |
-
logger.critical("Error in gff_parse() in annotation parser.")
|
| 258 |
-
logger.critical(e)
|
| 259 |
-
logger.critical(traceback.format_exc())
|
| 260 |
-
msgBox = QtWidgets.QMessageBox()
|
| 261 |
-
msgBox.setStyleSheet("font: " + str(GlobalSettings.mainWindow.fontSize) + "pt 'Arial'")
|
| 262 |
-
msgBox.setIcon(QtWidgets.QMessageBox.Icon.Critical)
|
| 263 |
-
msgBox.setWindowTitle("Fatal Error")
|
| 264 |
-
msgBox.setText("Fatal Error:\n"+str(e)+ "\n\nFor more information on this error, look at CASPER.log in the application folder.")
|
| 265 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Close)
|
| 266 |
-
msgBox.exec()
|
| 267 |
-
|
| 268 |
-
exit(-1)
|
| 269 |
-
|
| 270 |
-
# This function parses txt files and stores them in a dictionary
|
| 271 |
-
# It also creates a parallel dictionary to use in searching
|
| 272 |
-
# Precondition: ONLY TO BE USED WITH TXT FILES
|
| 273 |
-
def txt_parse(self):
|
| 274 |
-
try:
|
| 275 |
-
self.reg_dict.clear()
|
| 276 |
-
prevGenAccession = ""
|
| 277 |
-
indexNumber = 1
|
| 278 |
-
fileStream = open(self.annotationFileName)
|
| 279 |
-
buffer = ""
|
| 280 |
-
currentLocusTag = ""
|
| 281 |
-
para_dict_key_string = ""
|
| 282 |
-
|
| 283 |
-
while(True): # this loop breaks out when buffer string is empty
|
| 284 |
-
buffer = fileStream.readline()
|
| 285 |
-
|
| 286 |
-
if(buffer.startswith("#")): #skip lines that start with #
|
| 287 |
-
continue
|
| 288 |
-
else:
|
| 289 |
-
if(len(buffer) <= 2): # break out once we reach the end of the file
|
| 290 |
-
break
|
| 291 |
-
|
| 292 |
-
splitLine = buffer[:-1].split("\t")
|
| 293 |
-
|
| 294 |
-
# increment indexNumber when genomic access changes
|
| 295 |
-
if prevGenAccession != splitLine[6] and prevGenAccession != "":
|
| 296 |
-
indexNumber += 1
|
| 297 |
-
|
| 298 |
-
# if parsing on locus_tag, use the locus_tag as the key for the dict
|
| 299 |
-
if self.txtLocusTag:
|
| 300 |
-
currentLocusTag = splitLine[16]
|
| 301 |
-
values = [currentLocusTag, indexNumber, splitLine[0], int(splitLine[7]) - 1, int(splitLine[8]), splitLine[9]]
|
| 302 |
-
|
| 303 |
-
if currentLocusTag not in self.reg_dict:
|
| 304 |
-
self.reg_dict[currentLocusTag] = [values]
|
| 305 |
-
elif currentLocusTag in self.reg_dict:
|
| 306 |
-
self.reg_dict[currentLocusTag].append(values)
|
| 307 |
-
|
| 308 |
-
# if no locus_tag, parse on product_accession, use the product_accession as the key for the dict
|
| 309 |
-
elif not self.txtLocusTag:
|
| 310 |
-
currentLocusTag = splitLine[10]
|
| 311 |
-
values = [currentLocusTag, indexNumber, splitLine[0], int(splitLine[7]) - 1, int(splitLine[8]), splitLine[9]]
|
| 312 |
-
|
| 313 |
-
if currentLocusTag not in self.reg_dict:
|
| 314 |
-
self.reg_dict[currentLocusTag] = [values]
|
| 315 |
-
elif currentLocusTag in self.reg_dict:
|
| 316 |
-
self.reg_dict[currentLocusTag].append(values)
|
| 317 |
-
|
| 318 |
-
if splitLine[13] != '':
|
| 319 |
-
if para_dict_key_string == '':
|
| 320 |
-
para_dict_key_string = splitLine[13] + ';'
|
| 321 |
-
else:
|
| 322 |
-
para_dict_key_string = para_dict_key_string + splitLine[13] + ';'
|
| 323 |
-
|
| 324 |
-
# leaving this in for now, it's related accession
|
| 325 |
-
#if splitLine[12] != '':
|
| 326 |
-
# if para_dict_key_string == '':
|
| 327 |
-
# para_dict_key_string = splitLine[12] + ';'
|
| 328 |
-
# else:
|
| 329 |
-
# para_dict_key_string = para_dict_key_string + splitLine[12] + ';'
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
if splitLine[14] != '':
|
| 333 |
-
if para_dict_key_string == '':
|
| 334 |
-
para_dict_key_string = splitLine[14] + ';'
|
| 335 |
-
else:
|
| 336 |
-
para_dict_key_string = para_dict_key_string + splitLine[14] + ';'
|
| 337 |
-
|
| 338 |
-
para_dict_key_string = para_dict_key_string.replace(',', '')
|
| 339 |
-
# set the parallel dictionary's key string
|
| 340 |
-
#para_dict_key_string = splitLine[13] + ";" + splitLine[12] + ";" + splitLine[14]
|
| 341 |
-
|
| 342 |
-
# if the current line we're on has the data we want for the parellel dictionary, store it
|
| 343 |
-
if len(para_dict_key_string) > 3:
|
| 344 |
-
if para_dict_key_string[len(para_dict_key_string) - 1] == ';':
|
| 345 |
-
para_dict_key_string = para_dict_key_string[0:len(para_dict_key_string) - 1]
|
| 346 |
-
|
| 347 |
-
if para_dict_key_string not in self.para_dict: # make a new input into the dict
|
| 348 |
-
self.para_dict[para_dict_key_string] = [currentLocusTag]
|
| 349 |
-
elif para_dict_key_string in self.para_dict:
|
| 350 |
-
if currentLocusTag not in self.para_dict[para_dict_key_string]:
|
| 351 |
-
# only append it to the dict's list if it isn't currently in there
|
| 352 |
-
self.para_dict[para_dict_key_string].append(currentLocusTag)
|
| 353 |
-
|
| 354 |
-
para_dict_key_string = ""
|
| 355 |
-
prevGenAccession = splitLine[6]
|
| 356 |
-
self.max_chrom = indexNumber
|
| 357 |
-
except Exception as e:
|
| 358 |
-
logger.critical("Error in txt_parse() in annotation parser.")
|
| 359 |
-
logger.critical(e)
|
| 360 |
-
logger.critical(traceback.format_exc())
|
| 361 |
-
msgBox = QtWidgets.QMessageBox()
|
| 362 |
-
msgBox.setStyleSheet("font: " + str(GlobalSettings.mainWindow.fontSize) + "pt 'Arial'")
|
| 363 |
-
msgBox.setIcon(QtWidgets.QMessageBox.Icon.Critical)
|
| 364 |
-
msgBox.setWindowTitle("Fatal Error")
|
| 365 |
-
msgBox.setText("Fatal Error:\n"+str(e)+ "\n\nFor more information on this error, look at CASPER.log in the application folder.")
|
| 366 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Close)
|
| 367 |
-
msgBox.exec()
|
| 368 |
-
|
| 369 |
-
exit(-1)
|
| 370 |
-
|
| 371 |
-
# This function checks to see which file we are parsing
|
| 372 |
-
# It also checks whether to parse based on locus_tag or product accession (txt files only)
|
| 373 |
-
# Then it calls the respective parser functions used
|
| 374 |
-
def find_which_file_version(self):
|
| 375 |
-
try:
|
| 376 |
-
if self.annotationFileName == "" or GlobalSettings.mainWindow.annotation_files.currentText() == "None":
|
| 377 |
-
return -1
|
| 378 |
-
if "gff" in self.annotationFileName:
|
| 379 |
-
### gff file support currently deprecated
|
| 380 |
-
"""
|
| 381 |
-
self.isGff = True
|
| 382 |
-
self.gff_parse()
|
| 383 |
-
"""
|
| 384 |
-
print("Error: Wrong annotation file format")
|
| 385 |
-
return -1
|
| 386 |
-
|
| 387 |
-
elif "feature_table" in self.annotationFileName:
|
| 388 |
-
### feature table file support currently deprecated
|
| 389 |
-
# now that we know it's a txt file and not a gff, check and see if we will be parsing by locus tag or
|
| 390 |
-
# product accession
|
| 391 |
-
"""
|
| 392 |
-
fileStream = open(self.annotationFileName)
|
| 393 |
-
|
| 394 |
-
#skip all of the lines that start with #
|
| 395 |
-
buf = fileStream.readline()
|
| 396 |
-
while buf.startswith("#"):
|
| 397 |
-
buf = fileStream.readline()
|
| 398 |
-
|
| 399 |
-
# split it and see if the locus tag spot has data in it
|
| 400 |
-
split = buf.split("\t")
|
| 401 |
-
if split[16] != "": # if it does, we are parsing based on locus_tag
|
| 402 |
-
self.txtLocusTag = True
|
| 403 |
-
elif split[16] == "": # if not, we are parsing based on product accession
|
| 404 |
-
self.txtLocusTag = False
|
| 405 |
-
fileStream.close()
|
| 406 |
-
self.isTxt = True
|
| 407 |
-
self.txt_parse()
|
| 408 |
-
"""
|
| 409 |
-
print("Error: Wrong annotation file format")
|
| 410 |
-
return -1
|
| 411 |
-
elif "gbff" or "gbk" in self.annotationFileName:
|
| 412 |
-
return "gbff"
|
| 413 |
-
# return -1 to throw the error window in main
|
| 414 |
-
else:
|
| 415 |
-
return -1
|
| 416 |
-
except Exception as e:
|
| 417 |
-
logger.critical("Error in find_which_file_version() in annotation parser.")
|
| 418 |
-
logger.critical(e)
|
| 419 |
-
logger.critical(traceback.format_exc())
|
| 420 |
-
msgBox = QtWidgets.QMessageBox()
|
| 421 |
-
msgBox.setStyleSheet("font: " + str(GlobalSettings.mainWindow.fontSize) + "pt 'Arial'")
|
| 422 |
-
msgBox.setIcon(QtWidgets.QMessageBox.Icon.Critical)
|
| 423 |
-
msgBox.setWindowTitle("Fatal Error")
|
| 424 |
-
msgBox.setText("Fatal Error:\n"+str(e)+ "\n\nFor more information on this error, look at CASPER.log in the application folder.")
|
| 425 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Close)
|
| 426 |
-
msgBox.exec()
|
| 427 |
-
|
| 428 |
-
exit(-1)
|
| 429 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,987 +0,0 @@
|
|
| 1 |
-
import platform
|
| 2 |
-
# import controllers.ncbi as ncbi
|
| 3 |
-
import os
|
| 4 |
-
# from utils.Algorithms import get_table_headers
|
| 5 |
-
# from models.CSPRparser import CSPRparser
|
| 6 |
-
import glob
|
| 7 |
-
import models.GlobalSettings as GlobalSettings
|
| 8 |
-
from PyQt6 import QtWidgets, QtGui, QtCore, uic, QtGui
|
| 9 |
-
from utils.ui import scale_ui, center_ui, show_message, show_error
|
| 10 |
-
# from views.annotation_functions import *
|
| 11 |
-
# from views.AnnotationParser import Annotation_Parser
|
| 12 |
-
# from views.AnnotationWindow import AnnotationWindow
|
| 13 |
-
# import views.genomeBrowser as genomeBrowser
|
| 14 |
-
# from views.NewGenome import NewGenome
|
| 15 |
-
# from views.NewEndonuclease import NewEndonuclease
|
| 16 |
-
# from controllers.CoTargeting import CoTargeting
|
| 17 |
-
# from views.generateLib import genLibrary
|
| 18 |
-
# from controllers.Results import Results
|
| 19 |
-
# from views.export_tool import export_tool
|
| 20 |
-
# from views.closingWin import closingWindow
|
| 21 |
-
# from utils.web import ncbi_page, repo_page, ncbi_blast_page
|
| 22 |
-
# from controllers.populate_fna_files import PopulateFNAFiles
|
| 23 |
-
|
| 24 |
-
# logger = GlobalSettings.logger
|
| 25 |
-
|
| 26 |
-
fontSize = 12
|
| 27 |
-
|
| 28 |
-
class CMainWindow(QtWidgets.QMainWindow):
|
| 29 |
-
def __init__(self, settings):
|
| 30 |
-
try:
|
| 31 |
-
super(CMainWindow, self).__init__()
|
| 32 |
-
# uic.loadUi(os.path.join(self.settings.get_ui_dir(), 'startupCASPER.ui'), self)
|
| 33 |
-
# uic.loadUi(GlobalSettings.appdir + 'ui/CASPER_main.ui', self)
|
| 34 |
-
# print("path: ", GlobalSettings.appdir + 'ui/CASPER_main_copy_2.ui')
|
| 35 |
-
# uic.loadUi(GlobalSettings.appdir + 'ui/CASPER_main_copy_2.ui', self)
|
| 36 |
-
self.settings = settings
|
| 37 |
-
print("path: ", os.path.join(self.settings.get_ui_dir(), 'CASPER_main.ui'))
|
| 38 |
-
uic.loadUi(os.path.join(self.settings.get_ui_dir(), 'CASPER_main.ui'), self)
|
| 39 |
-
self.setWindowTitle("CASPER")
|
| 40 |
-
self.setWindowIcon(QtGui.QIcon(os.path.join(self.settings.get_assets_dir(), "cas9image.ico")))
|
| 41 |
-
|
| 42 |
-
# self.dbpath = ""
|
| 43 |
-
# self.inputstring = "" # This is the search string
|
| 44 |
-
# # self.info_path = settings.get_app_dir()
|
| 45 |
-
# # info_path = settings.get_app_dir()
|
| 46 |
-
# self.anno_name = ""
|
| 47 |
-
# self.endo_name = ""
|
| 48 |
-
# self.fontSize = 12
|
| 49 |
-
# self.org = ""
|
| 50 |
-
# self.TNumbers = {} # the T numbers from a kegg search
|
| 51 |
-
# self.orgcodes = {} # Stores the Kegg organism code by the format {full name : organism code}
|
| 52 |
-
# self.gene_list = {} # list of genes (no ides what they pertain to
|
| 53 |
-
# self.searches = {}
|
| 54 |
-
# self.checkBoxes = []
|
| 55 |
-
# self.genlib_list = [] # This list stores selected SeqFeatures from annotation window
|
| 56 |
-
# self.checked_info = {}
|
| 57 |
-
# self.check_ntseq_info = {} # the ntsequences that go along with the checked_info
|
| 58 |
-
# self.annotation_parser = Annotation_Parser()
|
| 59 |
-
# self.link_list = list() # the list of the downloadable links from the NCBI search
|
| 60 |
-
# self.organismDict = dict() # the dictionary for the links to download. Key is the description of the organism, value is the ID that can be found in link_list
|
| 61 |
-
# self.results_list = list()
|
| 62 |
-
# self.organismData = list()
|
| 63 |
-
# self.ncbi = ncbi.NCBI_search_tool()
|
| 64 |
-
|
| 65 |
-
# groupbox_style = """
|
| 66 |
-
# QGroupBox:title{subcontrol-origin: margin;
|
| 67 |
-
# left: 10px;
|
| 68 |
-
# padding: 0 5px 0 5px;}
|
| 69 |
-
# QGroupBox#Step1{border: 2px solid rgb(111,181,110);
|
| 70 |
-
# border-radius: 9px;
|
| 71 |
-
# margin-top: 10px;
|
| 72 |
-
# font: bold 14pt 'Arial';}
|
| 73 |
-
# """
|
| 74 |
-
|
| 75 |
-
# self.Step1.setStyleSheet(groupbox_style)
|
| 76 |
-
# self.Step2.setStyleSheet(groupbox_style.replace("Step1", "Step2"))
|
| 77 |
-
# self.Step3.setStyleSheet(groupbox_style.replace("Step1", "Step3"))
|
| 78 |
-
# self.CASPER_Navigation.setStyleSheet(groupbox_style.replace("Step1", "CASPER_Navigation").replace("solid","dashed").replace("rgb(111,181,110)","rgb(88,89,91)"))
|
| 79 |
-
|
| 80 |
-
# self.setWindowIcon(QtGui.QIcon(GlobalSettings.appdir + "cas9image.ico"))
|
| 81 |
-
# self.pushButton_FindTargets.clicked.connect(self.gather_settings)
|
| 82 |
-
# self.pushButton_ViewTargets.clicked.connect(self.view_results)
|
| 83 |
-
# self.pushButton_ViewTargets.setEnabled(False)
|
| 84 |
-
# self.GenerateLibrary.setEnabled(False)
|
| 85 |
-
# self.radioButton_Gene.clicked.connect(self.toggle_annotation)
|
| 86 |
-
# self.radioButton_Position.clicked.connect(self.toggle_annotation)
|
| 87 |
-
|
| 88 |
-
""" Connect functions to buttons """
|
| 89 |
-
# self.newGenome_button.clicked.connect(self.launch_newGenome) # Connect launch function to New Genome
|
| 90 |
-
# self.newEndo_button.clicked.connect(self.launch_newEndonuclease) # Connect launch function to New Endonuclease
|
| 91 |
-
# self.multitargeting_button.clicked.connect(self.changeto_multitargeting) # Connect launch function to Multitargeting
|
| 92 |
-
# self.populationAnalysis_button.clicked.connect(self.changeto_population_Analysis) # Connect launch function to PA
|
| 93 |
-
# self.GenerateLibrary.clicked.connect(self.prep_genlib)
|
| 94 |
-
# self.combineFiles_button.clicked.connect(self.launch_populate_fna_files)
|
| 95 |
-
|
| 96 |
-
""" Connect functions to actions (menu bar) """
|
| 97 |
-
# self.actionOpen_Genome_Browser.triggered.connect(self.launch_newGenomeBrowser)
|
| 98 |
-
# self.actionExit.triggered.connect(self.close_app)
|
| 99 |
-
# self.visit_repo.triggered.connect(repo_page)
|
| 100 |
-
# self.actionChange_Directory.triggered.connect(self.change_directory)
|
| 101 |
-
# self.actionNCBI.triggered.connect(ncbi_page)
|
| 102 |
-
# self.actionCasper2.triggered.connect(self.open_casper2_web_page)
|
| 103 |
-
# self.actionNCBI_BLAST.triggered.connect(ncbi_blast_page)
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
# self.progressBar.setMinimum(0)
|
| 108 |
-
# self.progressBar.setMaximum(100)
|
| 109 |
-
# self.progressBar.reset()
|
| 110 |
-
# self.Annotation_Window = AnnotationWindow(info_path)
|
| 111 |
-
# self.geneEntryField.setPlaceholderText("Example Inputs: \n\n"
|
| 112 |
-
# "Option 1: Feature (ID, Locus Tag, or Name)\n"
|
| 113 |
-
# "Example: 854068/YOL086C/ADH1 for S. cerevisiae alcohol dehydrogenase 1\n\n"
|
| 114 |
-
# "Option 2: Position (chromosome,start,stop)\n"
|
| 115 |
-
# "Example: 1,1,1000 for targeting chromosome 1, base pairs 1 to 1000\n\n"
|
| 116 |
-
# "Option 3: Sequence (must be within the selected organism)\n"
|
| 117 |
-
# "Example: Any nucleotide sequence between 100 and 10,000 base pairs.\n\n"
|
| 118 |
-
# "*Note: to multiplex, separate multiple queries by new lines*\n"
|
| 119 |
-
# "Example:\n"
|
| 120 |
-
# "1,1,1000\n"
|
| 121 |
-
# "5,1,500\n"
|
| 122 |
-
# "etc.")
|
| 123 |
-
|
| 124 |
-
# show functionalities on window
|
| 125 |
-
self.populate_fna_files = None
|
| 126 |
-
self._new_genome = None
|
| 127 |
-
# self.newEndonuclease = NewEndonuclease()
|
| 128 |
-
# self.CoTargeting = CoTargeting(info_path)
|
| 129 |
-
# self.Results = Results()
|
| 130 |
-
# self.export_tool_window = export_tool()
|
| 131 |
-
# self.genLib = genLibrary()
|
| 132 |
-
# self.myClosingWindow = closingWindow()
|
| 133 |
-
# self.genomebrowser = genomeBrowser.genomebrowser()
|
| 134 |
-
# self.launch_ncbi_button.clicked.connect(self.launch_ncbi)
|
| 135 |
-
|
| 136 |
-
# self.first_show = True
|
| 137 |
-
scale_ui(self, custom_scale_width=1150, custom_scale_height=650)
|
| 138 |
-
# self.show()
|
| 139 |
-
# self.load_dropdown_data()
|
| 140 |
-
print("MainWindow initialized")
|
| 141 |
-
except Exception as e:
|
| 142 |
-
show_error("Error in __init__() in main", e)
|
| 143 |
-
|
| 144 |
-
# def get_populate_fna_files(self):
|
| 145 |
-
# if self.populate_fna_files is None:
|
| 146 |
-
# self.populate_fna_files = PopulateFNAFiles(GlobalSettings.GlobalSettings1(GlobalSettings.appdir))
|
| 147 |
-
# return self.populate_fna_files
|
| 148 |
-
|
| 149 |
-
# def launch_populate_fna_files(self):
|
| 150 |
-
# self.get_populate_fna_files().show() # Ensure the window is shown
|
| 151 |
-
|
| 152 |
-
# this function prepares everything for the generate library function
|
| 153 |
-
# it is very similar to the gather settings, how ever it stores the data instead of calling the Annotation Window class
|
| 154 |
-
# it moves the data onto the generateLib function, and then opens that window
|
| 155 |
-
# def prep_genlib(self):
|
| 156 |
-
# # make sure the user actually inputs something
|
| 157 |
-
# try:
|
| 158 |
-
# inputstring = str(self.geneEntryField.toPlainText())
|
| 159 |
-
# if (inputstring.startswith("Example Inputs:") or inputstring == ""):
|
| 160 |
-
# show_message(
|
| 161 |
-
# fontSize=12,
|
| 162 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 163 |
-
# title="Error",
|
| 164 |
-
# message="No gene has been entered. Please enter a gene.",
|
| 165 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 166 |
-
# )
|
| 167 |
-
# return
|
| 168 |
-
# else:
|
| 169 |
-
# # standardize the input
|
| 170 |
-
# inputstring = inputstring.lower()
|
| 171 |
-
# found_matches_bool = True
|
| 172 |
-
# # call the respective function
|
| 173 |
-
# self.progressBar.setValue(10)
|
| 174 |
-
# if self.radioButton_Gene.isChecked():
|
| 175 |
-
# if len(self.genlib_list) > 0:
|
| 176 |
-
# found_matches_bool = True
|
| 177 |
-
# else:
|
| 178 |
-
# found_matches_bool = False
|
| 179 |
-
# elif self.radioButton_Position.isChecked() or self.radioButton_Sequence.isChecked():
|
| 180 |
-
# show_message(
|
| 181 |
-
# fontSize=12,
|
| 182 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 183 |
-
# title="Error",
|
| 184 |
-
# message="Generate Library can only work with feature searches.",
|
| 185 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 186 |
-
# )
|
| 187 |
-
# return
|
| 188 |
-
# """
|
| 189 |
-
# elif self.radioButton_Position.isChecked():
|
| 190 |
-
# pinput = inputstring.split(';')
|
| 191 |
-
# found_matches_bool = self.run_results("position", pinput,openAnnoWindow=False)
|
| 192 |
-
# elif self.radioButton_Sequence.isChecked():
|
| 193 |
-
# sinput = inputstring
|
| 194 |
-
# found_matches_bool = self.run_results("sequence", sinput, openAnnoWindow=False)
|
| 195 |
-
# """
|
| 196 |
-
# # if matches are found
|
| 197 |
-
# if found_matches_bool == True:
|
| 198 |
-
# # get the cspr file name
|
| 199 |
-
# cspr_file = self.organisms_to_files[self.orgChoice.currentText()][self.endoChoice.currentText()][0]
|
| 200 |
-
# if platform.system() == 'Windows':
|
| 201 |
-
# cspr_file = GlobalSettings.CSPR_DB + '\\' + cspr_file
|
| 202 |
-
# else:
|
| 203 |
-
# cspr_file = GlobalSettings.CSPR_DB + '/' + cspr_file
|
| 204 |
-
# kegg_non = 'non_kegg'
|
| 205 |
-
|
| 206 |
-
# # launch generateLib
|
| 207 |
-
# self.progressBar.setValue(100)
|
| 208 |
-
|
| 209 |
-
# # calculate the total number of matches found
|
| 210 |
-
# tempSum = len(self.genlib_list)
|
| 211 |
-
|
| 212 |
-
# # warn the user if the number is greater than 50
|
| 213 |
-
# if tempSum > 50:
|
| 214 |
-
# msgBox = QtWidgets.QMessageBox()
|
| 215 |
-
# msgBox.setStyleSheet("font: " + str(self.fontSize) + "pt 'Arial'")
|
| 216 |
-
# msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
|
| 217 |
-
# msgBox.setWindowTitle("Many Matches Found")
|
| 218 |
-
# msgBox.setText("More than 50 matches have been found. Continuing could cause a slow down...\n\n Do you wish to continue?")
|
| 219 |
-
# msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
|
| 220 |
-
# msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
|
| 221 |
-
# msgBox.exec()
|
| 222 |
-
|
| 223 |
-
# if (msgBox.result() == QtWidgets.QMessageBox.No):
|
| 224 |
-
# self.searches.clear()
|
| 225 |
-
# self.progressBar.setValue(0)
|
| 226 |
-
# return -2
|
| 227 |
-
|
| 228 |
-
# self.genLib.launch(self.genlib_list,cspr_file, kegg_non)
|
| 229 |
-
# else:
|
| 230 |
-
# self.progressBar.setValue(0)
|
| 231 |
-
# except Exception as e:
|
| 232 |
-
# show_error("Error in prep_genlib() in main", e)
|
| 233 |
-
|
| 234 |
-
# # Function for collecting the settings from the input field and transferring them to run_results
|
| 235 |
-
# def gather_settings(self):
|
| 236 |
-
# try:
|
| 237 |
-
# ### If user searches multiple times for the same thing, this avoids re-searching the entire annotation file
|
| 238 |
-
# check_org = self.orgChoice.currentText().lower()
|
| 239 |
-
# check_endo = self.endoChoice.currentText().lower()
|
| 240 |
-
# check_anno_name = self.annotation_files.currentText().lower()
|
| 241 |
-
# check_input = str(self.geneEntryField.toPlainText()).lower()
|
| 242 |
-
# if (check_input == self.inputstring and check_org == self.org and check_anno_name == self.anno_name and check_endo == self.endo_name):
|
| 243 |
-
# same_search = True
|
| 244 |
-
# else:
|
| 245 |
-
# self.org = check_org
|
| 246 |
-
# self.anno_name = check_anno_name
|
| 247 |
-
# self.inputstring = check_input
|
| 248 |
-
# self.endo_name = check_endo
|
| 249 |
-
# same_search = False
|
| 250 |
-
|
| 251 |
-
# # Error check: make sure the user actually inputs something
|
| 252 |
-
# if (self.inputstring.startswith("Example Inputs:") or self.inputstring == ""):
|
| 253 |
-
# show_message(
|
| 254 |
-
# fontSize=12,
|
| 255 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 256 |
-
# title="Error",
|
| 257 |
-
# message="No feature has been searched for. Please enter a search.",
|
| 258 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 259 |
-
# )
|
| 260 |
-
# return
|
| 261 |
-
# else:
|
| 262 |
-
|
| 263 |
-
# ### Remove additional scoring columns if necessary
|
| 264 |
-
# header = get_table_headers(self.Results.targetTable) # Returns headers of the target table in View Targets window
|
| 265 |
-
# col_indices = [header.index(x) for x in GlobalSettings.algorithms if x in header] # Returns the index(es) of the alternative scoring column(s) in the target table of View Targets window
|
| 266 |
-
# if len(col_indices) > 0: # If alternative scoring has been done
|
| 267 |
-
# for i in col_indices:
|
| 268 |
-
# self.Results.targetTable.removeColumn(i)
|
| 269 |
-
# self.Results.targetTable.resizeColumnsToContents()
|
| 270 |
-
|
| 271 |
-
# self.progressBar.setValue(10)
|
| 272 |
-
# if self.radioButton_Gene.isChecked():
|
| 273 |
-
# ginput = [x.strip() for x in self.inputstring.split('\n')] # Split search based on newline character and remove deadspace
|
| 274 |
-
# self.run_results("feature", ginput, same_search)
|
| 275 |
-
# elif self.radioButton_Position.isChecked():
|
| 276 |
-
# pinput = [x.strip() for x in self.inputstring.split('\n')] # Split search based on newline character and remove deadspace
|
| 277 |
-
# self.run_results("position", pinput, same_search)
|
| 278 |
-
# elif self.radioButton_Sequence.isChecked():
|
| 279 |
-
# sinput = self.inputstring
|
| 280 |
-
# self.run_results("sequence", sinput, same_search)
|
| 281 |
-
# except Exception as e:
|
| 282 |
-
# show_error("Error in gather_settings() in main", e)
|
| 283 |
-
|
| 284 |
-
# # ---- Following functions are for running the auxillary algorithms and windows ---- #
|
| 285 |
-
# # this function is parses the annotation file given, and then goes through and goes onto results
|
| 286 |
-
# # it will call other versions of collect_table_data and fill_table that work with these file types
|
| 287 |
-
# # this function should work with the any type of annotation file, besides kegg.
|
| 288 |
-
# # this assumes that the parsers all store the data the same way, which gff and feature table do
|
| 289 |
-
# # please make sure the genbank parser stores the data in the same way
|
| 290 |
-
# # so far the gff files seems to all be different. Need to think about how we want to parse it
|
| 291 |
-
# def run_results_own_ncbi_file(self, inputstring, fileName, same_search, openAnnoWindow=True):
|
| 292 |
-
# try:
|
| 293 |
-
# self.set_progress(35)
|
| 294 |
-
# self.results_list = self.annotation_parser.genbank_search(inputstring, same_search)
|
| 295 |
-
|
| 296 |
-
# cspr_file = self.organisms_to_files[self.orgChoice.currentText()][self.endoChoice.currentText()][0]
|
| 297 |
-
# cspr_file = os.path.join(GlobalSettings.CSPR_DB, cspr_file)
|
| 298 |
-
|
| 299 |
-
# own_cspr_parser = CSPRparser(cspr_file)
|
| 300 |
-
# own_cspr_parser.read_first_lines()
|
| 301 |
-
# if len(own_cspr_parser.karystatsList) != self.annotation_parser.max_chrom:
|
| 302 |
-
# show_message(
|
| 303 |
-
# fontSize=12,
|
| 304 |
-
# icon=QtWidgets.QMessageBox.Icon.Warning,
|
| 305 |
-
# title="Warning:",
|
| 306 |
-
# message="The number of chromosomes do not match. This could cause errors.",
|
| 307 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 308 |
-
# )
|
| 309 |
-
# self.set_progress(60)
|
| 310 |
-
|
| 311 |
-
# self.searches.clear()
|
| 312 |
-
|
| 313 |
-
# self.set_progress(75)
|
| 314 |
-
# if not self.results_list:
|
| 315 |
-
# show_message(
|
| 316 |
-
# fontSize=12,
|
| 317 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 318 |
-
# title="No Matches Found",
|
| 319 |
-
# message="No matches found with that search, please try again.",
|
| 320 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 321 |
-
# )
|
| 322 |
-
# self.set_progress(0)
|
| 323 |
-
# return False if not openAnnoWindow else None
|
| 324 |
-
|
| 325 |
-
# self.set_progress(80)
|
| 326 |
-
|
| 327 |
-
# return self.Annotation_Window.fill_table_nonKegg(self, self.results_list) if openAnnoWindow else True
|
| 328 |
-
# except Exception as e:
|
| 329 |
-
# show_error(f"Error in run_results_own_ncbi_file() in main.", e)
|
| 330 |
-
|
| 331 |
-
# def set_progress(self, value):
|
| 332 |
-
# self.progressBar.setValue(value)
|
| 333 |
-
|
| 334 |
-
# def run_results(self, inputtype, inputstring, same_search, openAnnoWindow=True):
|
| 335 |
-
# try:
|
| 336 |
-
# file_name = self.annotation_files.currentText()
|
| 337 |
-
# for file in glob.glob(GlobalSettings.CSPR_DB + "/**/*.gb*", recursive=True):
|
| 338 |
-
# if file_name in file:
|
| 339 |
-
# self.annotation_parser.annotationFileName = file
|
| 340 |
-
# break
|
| 341 |
-
# self.Results.annotation_path = self.annotation_parser.annotationFileName
|
| 342 |
-
|
| 343 |
-
# progvalue = 15
|
| 344 |
-
# self.searches = {}
|
| 345 |
-
# self.gene_list = {}
|
| 346 |
-
# self.progressBar.setValue(progvalue)
|
| 347 |
-
|
| 348 |
-
# try:
|
| 349 |
-
# self.Results.endonucleaseBox.currentIndexChanged.disconnect()
|
| 350 |
-
# except Exception as e:
|
| 351 |
-
# pass
|
| 352 |
-
# # set Results endo combo box
|
| 353 |
-
# self.Results.endonucleaseBox.clear()
|
| 354 |
-
|
| 355 |
-
# # set the results window endoChoice box menu
|
| 356 |
-
# # set the mainWindow's endoChoice first, and then loop through and set the rest of them
|
| 357 |
-
# self.Results.endonucleaseBox.addItem(self.endoChoice.currentText())
|
| 358 |
-
# for item in self.organisms_to_endos[str(self.orgChoice.currentText())]:
|
| 359 |
-
# if item != self.Results.endonucleaseBox.currentText():
|
| 360 |
-
# self.Results.endonucleaseBox.addItem(item)
|
| 361 |
-
|
| 362 |
-
# self.Results.endonucleaseBox.currentIndexChanged.connect(self.Results.changeEndonuclease)
|
| 363 |
-
# self.Results.get_endo_data()
|
| 364 |
-
|
| 365 |
-
# # self.Results.change_start_end_button.setEnabled(False)
|
| 366 |
-
# self.Results.displayGeneViewer.setChecked(0)
|
| 367 |
-
|
| 368 |
-
# if inputtype == "feature":
|
| 369 |
-
# fileType = self.annotation_parser.find_which_file_version()
|
| 370 |
-
|
| 371 |
-
# # if the parser retuns the 'wrong file type' error
|
| 372 |
-
# if fileType == -1:
|
| 373 |
-
# show_message(
|
| 374 |
-
# fontSize=12,
|
| 375 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 376 |
-
# title="Error",
|
| 377 |
-
# message="Feature search requires a GenBank formatted annotation file. Please select a file from the dropdown menu or search by position",
|
| 378 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 379 |
-
# )
|
| 380 |
-
# self.progressBar.setValue(0)
|
| 381 |
-
# return
|
| 382 |
-
|
| 383 |
-
# # make sure an annotation file has been selected
|
| 384 |
-
# if self.annotation_files.currentText() == "None":
|
| 385 |
-
# show_message(
|
| 386 |
-
# fontSize=12,
|
| 387 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 388 |
-
# title="Error",
|
| 389 |
-
# message="Search by feature requires a GenBank annotation file. Please select one from the dropdown menu or search by position.",
|
| 390 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 391 |
-
# )
|
| 392 |
-
# self.progressBar.setValue(0)
|
| 393 |
-
# return
|
| 394 |
-
|
| 395 |
-
# # this now just goes onto the other version of run_results
|
| 396 |
-
# myBool = self.run_results_own_ncbi_file(inputstring, self.annotation_files.currentText(), same_search, openAnnoWindow=openAnnoWindow)
|
| 397 |
-
# if not openAnnoWindow:
|
| 398 |
-
# return myBool
|
| 399 |
-
# else:
|
| 400 |
-
# self.progressBar.setValue(0)
|
| 401 |
-
# return
|
| 402 |
-
|
| 403 |
-
# if inputtype == "position":
|
| 404 |
-
# full_org = str(self.orgChoice.currentText())
|
| 405 |
-
# self.checked_info.clear()
|
| 406 |
-
# self.check_ntseq_info.clear()
|
| 407 |
-
|
| 408 |
-
# for item in inputstring: # Loop through each search
|
| 409 |
-
# searchIndices = [x.strip() for x in item.split(',')] # Parse input query
|
| 410 |
-
|
| 411 |
-
# if len(searchIndices) != 3:
|
| 412 |
-
# show_message(
|
| 413 |
-
# fontSize=12,
|
| 414 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 415 |
-
# title="Error",
|
| 416 |
-
# message="There are 3 arguments required for this function: chromosome, start position, and end position.",
|
| 417 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 418 |
-
# )
|
| 419 |
-
# self.progressBar.setValue(0)
|
| 420 |
-
# return
|
| 421 |
-
|
| 422 |
-
# if not searchIndices[0].isdigit() or not searchIndices[1].isdigit() or not searchIndices[2].isdigit():
|
| 423 |
-
# show_message(
|
| 424 |
-
# fontSize=12,
|
| 425 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 426 |
-
# title="Error",
|
| 427 |
-
# message="The positions given must be integers. Please try again.",
|
| 428 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 429 |
-
# )
|
| 430 |
-
# self.progressBar.setValue(0)
|
| 431 |
-
# return
|
| 432 |
-
# elif int(searchIndices[1]) >= int(searchIndices[2]):
|
| 433 |
-
# show_message(
|
| 434 |
-
# fontSize=12,
|
| 435 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 436 |
-
# title="Error",
|
| 437 |
-
# message="The start index must be less than the end index.",
|
| 438 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 439 |
-
# )
|
| 440 |
-
# self.progressBar.setValue(0)
|
| 441 |
-
# return
|
| 442 |
-
# elif abs(int(searchIndices[2])-int(searchIndices[1])) > 50000:
|
| 443 |
-
# show_message(
|
| 444 |
-
# fontSize=12,
|
| 445 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 446 |
-
# title="Error",
|
| 447 |
-
# message="The search range must be less than 50,000 nt.",
|
| 448 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 449 |
-
# )
|
| 450 |
-
# self.progressBar.setValue(0)
|
| 451 |
-
# return
|
| 452 |
-
# elif int(searchIndices[0]) > self.annotation_parser.get_max_chrom():
|
| 453 |
-
# show_message(
|
| 454 |
-
# fontSize=12,
|
| 455 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 456 |
-
# title="Error",
|
| 457 |
-
# message="Chromosome %s does not exist in the selected annotation file." % searchIndices[0],
|
| 458 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 459 |
-
# )
|
| 460 |
-
# self.progressBar.setValue(0)
|
| 461 |
-
# return
|
| 462 |
-
# # append the data into the checked_info
|
| 463 |
-
# tempString = 'chrom: ' + str(searchIndices[0]) + ',start: ' + str(searchIndices[1]) + ',end: ' + str(searchIndices[2])
|
| 464 |
-
# self.checked_info[tempString] = (int(searchIndices[0]), int(searchIndices[1])-1, int(searchIndices[2]))
|
| 465 |
-
|
| 466 |
-
# self.progressBar.setValue(50)
|
| 467 |
-
# self.Results.transfer_data(full_org, self.organisms_to_files[full_org], [str(self.endoChoice.currentText())], os.getcwd(), self.checked_info, self.check_ntseq_info,inputtype)
|
| 468 |
-
# self.Results.load_gene_viewer()
|
| 469 |
-
# self.progressBar.setValue(100)
|
| 470 |
-
# self.pushButton_ViewTargets.setEnabled(True)
|
| 471 |
-
# self.GenerateLibrary.setEnabled(True)
|
| 472 |
-
|
| 473 |
-
# if inputtype == "sequence":
|
| 474 |
-
# fileType = self.annotation_parser.find_which_file_version()
|
| 475 |
-
|
| 476 |
-
# if fileType == -1:
|
| 477 |
-
# show_message(
|
| 478 |
-
# fontSize=12,
|
| 479 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 480 |
-
# title="Error",
|
| 481 |
-
# message="Search by sequence requires a GenBank annotation file. Please select one from the dropdown menu or search by position.",
|
| 482 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 483 |
-
# )
|
| 484 |
-
# self.progressBar.setValue(0)
|
| 485 |
-
# return
|
| 486 |
-
# if self.annotation_files.currentText() == "None":
|
| 487 |
-
# show_message(
|
| 488 |
-
# fontSize=12,
|
| 489 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 490 |
-
# title="Error",
|
| 491 |
-
# message="Search by sequence requires a GenBank annotation file. Please select one from the dropdown menu or search by position.",
|
| 492 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 493 |
-
# )
|
| 494 |
-
# self.progressBar.setValue(0)
|
| 495 |
-
# return
|
| 496 |
-
|
| 497 |
-
# checkString = 'AGTCN'
|
| 498 |
-
# full_org = str(self.orgChoice.currentText())
|
| 499 |
-
# self.checked_info.clear()
|
| 500 |
-
# self.progressBar.setValue(10)
|
| 501 |
-
# inputstring = inputstring.replace('\n','').upper().strip()
|
| 502 |
-
|
| 503 |
-
# for letter in inputstring:
|
| 504 |
-
# if letter not in checkString:
|
| 505 |
-
# show_message(
|
| 506 |
-
# fontSize=12,
|
| 507 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 508 |
-
# title="Error",
|
| 509 |
-
# message="The sequence must consist of A, G, T, C, or N. No other characters are allowed.",
|
| 510 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 511 |
-
# )
|
| 512 |
-
# self.progressBar.setValue(0)
|
| 513 |
-
# return
|
| 514 |
-
|
| 515 |
-
# if len(inputstring) < 100:
|
| 516 |
-
# show_message(
|
| 517 |
-
# fontSize=12,
|
| 518 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 519 |
-
# title="Error",
|
| 520 |
-
# message="The sequence given is too small. At least 100 characters are required.",
|
| 521 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 522 |
-
# )
|
| 523 |
-
# self.progressBar.setValue(0)
|
| 524 |
-
# return
|
| 525 |
-
|
| 526 |
-
# if len(inputstring) > 10000:
|
| 527 |
-
# show_message(
|
| 528 |
-
# fontSize=12,
|
| 529 |
-
# icon=QtWidgets.QMessageBox.Icon.Question,
|
| 530 |
-
# title="Large Sequence Detected",
|
| 531 |
-
# message="The sequence given is too large one.\n\nPlease input a sequence less than 10kb in length.",
|
| 532 |
-
# button=QtWidgets.QMessageBox.StandardButton.Yes
|
| 533 |
-
# )
|
| 534 |
-
# self.progressBar.setValue(0)
|
| 535 |
-
# return
|
| 536 |
-
|
| 537 |
-
# self.progressBar.setValue(30)
|
| 538 |
-
|
| 539 |
-
# # Check the GBFF file for the sequence
|
| 540 |
-
# my_check = self.annotation_parser.get_sequence_info(inputstring)
|
| 541 |
-
|
| 542 |
-
# self.progressBar.setValue(55) # Update progress bar
|
| 543 |
-
|
| 544 |
-
# if type(my_check) == bool:
|
| 545 |
-
# show_message(
|
| 546 |
-
# fontSize=12,
|
| 547 |
-
# icon=QtWidgets.QMessageBox.Icon.Question,
|
| 548 |
-
# title="Sequence Not Found",
|
| 549 |
-
# message="The sequence entered was not found.\n\nPlease input a sequence that is in the selected organism.",
|
| 550 |
-
# button=QtWidgets.QMessageBox.StandardButton.Yes
|
| 551 |
-
# )
|
| 552 |
-
# self.progressBar.setValue(0)
|
| 553 |
-
# return
|
| 554 |
-
|
| 555 |
-
# else:
|
| 556 |
-
# tempString = 'chrom: ' + str(my_check[0]) + ',start: ' + str(my_check[1]) + ',end: ' + str(my_check[2])
|
| 557 |
-
# self.checked_info[tempString] = (int(my_check[0]), int(my_check[1])-1, int(my_check[2]))
|
| 558 |
-
|
| 559 |
-
# self.progressBar.setValue(75)
|
| 560 |
-
|
| 561 |
-
# self.Results.transfer_data(full_org, self.organisms_to_files[full_org], [str(self.endoChoice.currentText())], os.getcwd(), self.checked_info, self.check_ntseq_info, inputtype)
|
| 562 |
-
# self.Results.load_gene_viewer()
|
| 563 |
-
# self.progressBar.setValue(100)
|
| 564 |
-
# self.pushButton_ViewTargets.setEnabled(True)
|
| 565 |
-
# self.GenerateLibrary.setEnabled(True)
|
| 566 |
-
# except Exception as e:
|
| 567 |
-
# show_error("Error in run_results() in main", e)
|
| 568 |
-
|
| 569 |
-
# def handle_feature_search(self, input_string, open_anno_window):
|
| 570 |
-
# file_type = self.annotation_parser.find_which_file_version()
|
| 571 |
-
# if file_type == -1 or self.annotation_files.currentText() == "None":
|
| 572 |
-
# self.show_error_message("Feature search requires a GenBank formatted annotation file.")
|
| 573 |
-
# return False
|
| 574 |
-
|
| 575 |
-
# return self.run_results_own_ncbi_file(input_string, self.annotation_files.currentText(), same_search, open_anno_window)
|
| 576 |
-
|
| 577 |
-
# def launch_newGenome(self):
|
| 578 |
-
# try:
|
| 579 |
-
# # Update endo list
|
| 580 |
-
# self.get_new_genome().fillEndo()
|
| 581 |
-
# if self.get_new_genome().first_show:
|
| 582 |
-
# center_ui(self.get_new_genome())
|
| 583 |
-
# self.get_new_genome().first_show = False
|
| 584 |
-
# self.hide()
|
| 585 |
-
# self.get_new_genome().show()
|
| 586 |
-
# except Exception as e:
|
| 587 |
-
# show_error("Error in launch_newGenome() in main", e)
|
| 588 |
-
|
| 589 |
-
# def launch_newEndonuclease(self):
|
| 590 |
-
# try:
|
| 591 |
-
# center_ui(self.newEndonuclease)
|
| 592 |
-
# self.newEndonuclease.show()
|
| 593 |
-
# self.newEndonuclease.activateWindow()
|
| 594 |
-
# except Exception as e:
|
| 595 |
-
# show_error("Error in launch_newEndonuclease() in main", e)
|
| 596 |
-
|
| 597 |
-
# #launch genome browser tool
|
| 598 |
-
# def launch_newGenomeBrowser(self):
|
| 599 |
-
# try:
|
| 600 |
-
# self.genomebrowser.createGraph(self)
|
| 601 |
-
# except Exception as e:
|
| 602 |
-
# show_error("Error in launch_newGenomeBrowser() in main", e)
|
| 603 |
-
|
| 604 |
-
# def launch_ncbi(self):
|
| 605 |
-
# try:
|
| 606 |
-
# show_message(
|
| 607 |
-
# fontSize=12,
|
| 608 |
-
# icon=QtWidgets.QMessageBox.Icon.Information,
|
| 609 |
-
# title="Note:",
|
| 610 |
-
# message="NCBI Annotation Guidelines:\n\nDownload annotation files of the exact species and strain used in Analyze New Genome.\n\nMismatched annotation files will inhibit downstream analyses.",
|
| 611 |
-
# button=QtWidgets.QMessageBox.StandardButton.Ok
|
| 612 |
-
# )
|
| 613 |
-
# if self.ncbi.first_show:
|
| 614 |
-
# self.ncbi.first_show = False
|
| 615 |
-
# center_ui(self.ncbi)
|
| 616 |
-
|
| 617 |
-
# self.ncbi.show()
|
| 618 |
-
# self.ncbi.activateWindow()
|
| 619 |
-
# except Exception as e:
|
| 620 |
-
# show_error("launch_ncbi() in main", e)
|
| 621 |
-
|
| 622 |
-
# # this function does the same stuff that the other collect_table_data does, but works with the other types of files
|
| 623 |
-
# def collect_table_data_nonkegg(self):
|
| 624 |
-
# try:
|
| 625 |
-
# # start out the same as the other collect_table_data
|
| 626 |
-
# self.checked_info.clear()
|
| 627 |
-
# self.genlib_list.clear()
|
| 628 |
-
# self.check_ntseq_info.clear()
|
| 629 |
-
# full_org = str(self.orgChoice.currentText())
|
| 630 |
-
# holder = ()
|
| 631 |
-
# selected_indices = []
|
| 632 |
-
# selected_rows = self.Annotation_Window.tableWidget.selectionModel().selectedRows()
|
| 633 |
-
# for ind in sorted(selected_rows):
|
| 634 |
-
# selected_indices.append(ind.row())
|
| 635 |
-
|
| 636 |
-
# for item in self.checkBoxes:
|
| 637 |
-
# feature = item[1]
|
| 638 |
-
# # If inidices of checkBoxes list and selected rows in table match...
|
| 639 |
-
# if item[2] in selected_indices:
|
| 640 |
-
# holder = (item[0],int(feature.location.start),int(feature.location.end)) # Tuple order: Feature chromosome/scaffold number, feature start, feature end
|
| 641 |
-
# ### If locus tag available, combine with gene name to create dict key
|
| 642 |
-
# if 'locus_tag' in feature.qualifiers:
|
| 643 |
-
# tag = feature.qualifiers['locus_tag'][0]
|
| 644 |
-
# key = tag + ": " + get_name(feature)
|
| 645 |
-
# else:
|
| 646 |
-
# key = get_name(feature)
|
| 647 |
-
# self.checked_info[key] = holder
|
| 648 |
-
# self.genlib_list.append((item[0],feature)) # Tuple order: Feature chromosome/scaffold number, SeqFeature object
|
| 649 |
-
# else:
|
| 650 |
-
# # If item was not selected in the table, go to the next item
|
| 651 |
-
# continue
|
| 652 |
-
|
| 653 |
-
# # now call transfer data
|
| 654 |
-
# self.progressBar.setValue(95)
|
| 655 |
-
# self.Results.transfer_data(full_org, self.organisms_to_files[full_org], [str(self.endoChoice.currentText())], os.getcwd(),
|
| 656 |
-
# self.checked_info, self.check_ntseq_info,inputtype="feature")
|
| 657 |
-
# self.Results.load_gene_viewer()
|
| 658 |
-
|
| 659 |
-
# self.progressBar.setValue(100)
|
| 660 |
-
# self.pushButton_ViewTargets.setEnabled(True)
|
| 661 |
-
# self.GenerateLibrary.setEnabled(True)
|
| 662 |
-
# except Exception as e:
|
| 663 |
-
# show_error("Error in collect_table_data_nonkegg() in main", e)
|
| 664 |
-
|
| 665 |
-
# def separate_line(self, input_string):
|
| 666 |
-
# try:
|
| 667 |
-
# export_array = []
|
| 668 |
-
# while True:
|
| 669 |
-
# index = input_string.find('\n')
|
| 670 |
-
# if index == -1:
|
| 671 |
-
# if len(input_string) == 0:
|
| 672 |
-
# return export_array
|
| 673 |
-
# else:
|
| 674 |
-
# export_array.append(input_string)
|
| 675 |
-
# return export_array
|
| 676 |
-
# export_array.append(input_string[:index])
|
| 677 |
-
# input_string = input_string[index + 1:]
|
| 678 |
-
# except Exception as e:
|
| 679 |
-
# show_error("Error in seperate_line() in main", e)
|
| 680 |
-
|
| 681 |
-
# def removeWhiteSpace(self, strng):
|
| 682 |
-
# try:
|
| 683 |
-
# while True:
|
| 684 |
-
# if len(strng) == 0 or (strng[0] != " " and strng[0] != "\n"):
|
| 685 |
-
# break
|
| 686 |
-
# strng = strng[1:]
|
| 687 |
-
# while True:
|
| 688 |
-
# if len(strng) == 0 or (strng[len(strng) - 1] != " " and strng[0] != "\n"):
|
| 689 |
-
# return strng
|
| 690 |
-
# strng = strng[:len(strng) - 1]
|
| 691 |
-
# except Exception as e:
|
| 692 |
-
# show_error("Error in removeWhiteSpace() in main", e)
|
| 693 |
-
|
| 694 |
-
# # Function to enable and disable the Annotation function if searching by position or sequence
|
| 695 |
-
# def toggle_annotation(self):
|
| 696 |
-
# try:
|
| 697 |
-
# if self.radioButton_Gene.isChecked():
|
| 698 |
-
# self.Step2.setEnabled(True)
|
| 699 |
-
# else:
|
| 700 |
-
# self.Step2.setEnabled(True)
|
| 701 |
-
# except Exception as e:
|
| 702 |
-
# show_error("Error in toggle_annotation() in main", e)
|
| 703 |
-
|
| 704 |
-
# def fill_annotation_dropdown(self):
|
| 705 |
-
# try:
|
| 706 |
-
# #recursive search for all GenBank files in casper db folder
|
| 707 |
-
# self.annotation_files.clear()
|
| 708 |
-
# annotation_files = glob.glob(GlobalSettings.CSPR_DB + "/**/*.gb*", recursive=True)
|
| 709 |
-
# if platform.system() == "Windows":
|
| 710 |
-
# for i in range(len(annotation_files)):
|
| 711 |
-
# annotation_files[i] = annotation_files[i].replace("/","\\")
|
| 712 |
-
# annotation_files[i] = annotation_files[i][annotation_files[i].rfind("\\") + 1:]
|
| 713 |
-
# else:
|
| 714 |
-
# for i in range(len(annotation_files)):
|
| 715 |
-
# annotation_files[i] = annotation_files[i].replace("\\","/")
|
| 716 |
-
# annotation_files[i] = annotation_files[i][annotation_files[i].rfind("/") + 1:]
|
| 717 |
-
|
| 718 |
-
# annotation_files.sort(key=str.lower)
|
| 719 |
-
# self.annotation_files.addItems(annotation_files)
|
| 720 |
-
# self.annotation_files.addItems(["None"])
|
| 721 |
-
# except Exception as e:
|
| 722 |
-
# show_error("Error in fill_annotation_dropdown() in main", e)
|
| 723 |
-
|
| 724 |
-
# def make_dictonary(self):
|
| 725 |
-
# try:
|
| 726 |
-
# url = "https://www.genome.jp/dbget-bin/get_linkdb?-t+genes+gn:" + self.TNumbers[
|
| 727 |
-
# self.Annotations_Organism.currentText()]
|
| 728 |
-
# source_code = requests.get(url, verify=False)
|
| 729 |
-
# plain_text = source_code.text
|
| 730 |
-
# buf = io.StringIO(plain_text)
|
| 731 |
-
|
| 732 |
-
# while True:
|
| 733 |
-
# line = buf.readline()
|
| 734 |
-
# if line[0] == "-":
|
| 735 |
-
# break
|
| 736 |
-
# while True:
|
| 737 |
-
# line = buf.readline()
|
| 738 |
-
# if line[1] != "a":
|
| 739 |
-
# return
|
| 740 |
-
# line = line[line.find(">") + 1:]
|
| 741 |
-
# seq = line[line.find(":") + 1:line.find("<")]
|
| 742 |
-
# line = line[line.find(">") + 1:]
|
| 743 |
-
|
| 744 |
-
# i = 0
|
| 745 |
-
# while True:
|
| 746 |
-
# if line[i] == " ":
|
| 747 |
-
# i = i + 1
|
| 748 |
-
# else:
|
| 749 |
-
# break
|
| 750 |
-
# key = line[i:line.find("\n") - 1]
|
| 751 |
-
# if key in self.gene_list:
|
| 752 |
-
# if seq not in self.gene_list[key]:
|
| 753 |
-
# self.gene_list[key].append(seq)
|
| 754 |
-
# else:
|
| 755 |
-
# self.gene_list[key] = [seq]
|
| 756 |
-
# z = 5
|
| 757 |
-
# except Exception as e:
|
| 758 |
-
# show_error("Error in make_dictionary() in main", e)
|
| 759 |
-
|
| 760 |
-
# def organism_finder(self, long_str):
|
| 761 |
-
# try:
|
| 762 |
-
# semi = long_str.find(";")
|
| 763 |
-
# index = 1
|
| 764 |
-
# while True:
|
| 765 |
-
# if long_str[semi - index] == " ":
|
| 766 |
-
# break
|
| 767 |
-
# index = index + 1
|
| 768 |
-
# return long_str[:semi - index]
|
| 769 |
-
# except Exception as e:
|
| 770 |
-
# show_error("Error in organism_finder() in main", e)
|
| 771 |
-
|
| 772 |
-
# # This method is for testing the execution of a button call to make sure the button is linked properly
|
| 773 |
-
# def testexe(self):
|
| 774 |
-
# try:
|
| 775 |
-
# msgBox = QtWidgets.QMessageBox()
|
| 776 |
-
# msgBox.setStyleSheet("font: " + str(self.fontSize) + "pt 'Arial'")
|
| 777 |
-
# msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
|
| 778 |
-
# msgBox.setWindowTitle("Extract!")
|
| 779 |
-
# msgBox.setText(
|
| 780 |
-
# "Are you sure you want to quit?")
|
| 781 |
-
# msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
|
| 782 |
-
# msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
|
| 783 |
-
# msgBox.exec()
|
| 784 |
-
|
| 785 |
-
# if msgBox.result() == QtWidgets.QMessageBox.Yes:
|
| 786 |
-
# # print(self.orgChoice.currentText())
|
| 787 |
-
# sys.exit()
|
| 788 |
-
# else:
|
| 789 |
-
# pass
|
| 790 |
-
# except Exception as e:
|
| 791 |
-
# show_error("Error in testexe() in main", e)
|
| 792 |
-
|
| 793 |
-
# def getData(self):
|
| 794 |
-
# try:
|
| 795 |
-
# try:
|
| 796 |
-
# self.orgChoice.currentIndexChanged.disconnect()
|
| 797 |
-
# except Exception as e:
|
| 798 |
-
# pass
|
| 799 |
-
|
| 800 |
-
# self.orgChoice.clear()
|
| 801 |
-
# self.endoChoice.clear()
|
| 802 |
-
# mypath = os.getcwd()
|
| 803 |
-
# found = False
|
| 804 |
-
# self.dbpath = mypath
|
| 805 |
-
# onlyfiles = [str(f) for f in os.listdir(mypath) if os.path.isfile(os.path.join(mypath, f))]
|
| 806 |
-
# onlyfiles.sort(key=str.lower)
|
| 807 |
-
# self.organisms_to_files = {}
|
| 808 |
-
# self.organisms_to_endos = {}
|
| 809 |
-
# first = True
|
| 810 |
-
# for file in onlyfiles:
|
| 811 |
-
# if file.find('.cspr') != -1:
|
| 812 |
-
# if first == True:
|
| 813 |
-
# first = False
|
| 814 |
-
# found = True
|
| 815 |
-
# newname = file[0:-4]
|
| 816 |
-
# endo = newname[newname.rfind("_")+1:-1]
|
| 817 |
-
# hold = open(file, 'r')
|
| 818 |
-
# buf = (hold.readline())
|
| 819 |
-
# buf = str(buf)
|
| 820 |
-
# buf = buf.strip()
|
| 821 |
-
# species = buf.replace("GENOME: ",'')
|
| 822 |
-
|
| 823 |
-
# if species in self.organisms_to_files:
|
| 824 |
-
# self.organisms_to_files[species][endo] = [file, file.replace(".cspr", "_repeats.db")]
|
| 825 |
-
# else:
|
| 826 |
-
# self.organisms_to_files[species] = {}
|
| 827 |
-
# self.organisms_to_files[species][endo] = [file, file.replace(".cspr", "_repeats.db")]
|
| 828 |
-
|
| 829 |
-
# if species in self.organisms_to_endos:
|
| 830 |
-
# self.organisms_to_endos[species].append(endo)
|
| 831 |
-
# else:
|
| 832 |
-
# self.organisms_to_endos[species] = [endo]
|
| 833 |
-
# if self.orgChoice.findText(species) == -1:
|
| 834 |
-
# self.orgChoice.addItem(species)
|
| 835 |
-
|
| 836 |
-
# if found == False:
|
| 837 |
-
# return False
|
| 838 |
-
|
| 839 |
-
# self.endoChoice.clear()
|
| 840 |
-
# self.endoChoice.addItems(self.organisms_to_endos[str(self.orgChoice.currentText())])
|
| 841 |
-
# self.orgChoice.currentIndexChanged.connect(self.changeEndos)
|
| 842 |
-
# except Exception as e:
|
| 843 |
-
# show_error("Error in getData() in main.", e)
|
| 844 |
-
|
| 845 |
-
# def changeEndos(self):
|
| 846 |
-
# try:
|
| 847 |
-
# if self.orgChoice.currentText() != "Custom Input Sequences":
|
| 848 |
-
# self.Step2.setEnabled(True)
|
| 849 |
-
# self.endoChoice.setEnabled(True)
|
| 850 |
-
# self.radioButton_Gene.show()
|
| 851 |
-
# self.radioButton_Position.show()
|
| 852 |
-
# self.endoChoice.clear()
|
| 853 |
-
# self.endoChoice.addItems(self.organisms_to_endos[str(self.orgChoice.currentText())])
|
| 854 |
-
# else:
|
| 855 |
-
# self.Step2.setEnabled(False)
|
| 856 |
-
# self.endoChoice.clear()
|
| 857 |
-
# self.endoChoice.setEnabled(False)
|
| 858 |
-
# self.radioButton_Gene.hide()
|
| 859 |
-
# self.radioButton_Position.hide()
|
| 860 |
-
# except Exception as e:
|
| 861 |
-
# show_error("Error in changeEndos() in main", e)
|
| 862 |
-
|
| 863 |
-
# def change_directory(self):
|
| 864 |
-
# try:
|
| 865 |
-
# mydir = QtWidgets.QFileDialog.getExistingDirectory(
|
| 866 |
-
# None, "Open a folder...", self.dbpath, QtWidgets.QFileDialog.Option.ShowDirsOnly)
|
| 867 |
-
|
| 868 |
-
# if not os.path.isdir(mydir):
|
| 869 |
-
# show_message(
|
| 870 |
-
# fontSize=12,
|
| 871 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 872 |
-
# title="Not a directory",
|
| 873 |
-
# message="The directory you selected does not exist."
|
| 874 |
-
# )
|
| 875 |
-
# return
|
| 876 |
-
|
| 877 |
-
# if not any(file.endswith(".cspr") for file in os.listdir(mydir)):
|
| 878 |
-
# show_message(
|
| 879 |
-
# fontSize=12,
|
| 880 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 881 |
-
# title="Directory is invalid!",
|
| 882 |
-
# message="You must select a directory with CSPR Files!"
|
| 883 |
-
# )
|
| 884 |
-
# return
|
| 885 |
-
|
| 886 |
-
# os.chdir(mydir)
|
| 887 |
-
# mydir = mydir.replace("/", "\\") if platform.system() == "Windows" else mydir
|
| 888 |
-
# GlobalSettings.CSPR_DB = mydir
|
| 889 |
-
|
| 890 |
-
# GlobalSettings.MTWin.directory = mydir
|
| 891 |
-
# GlobalSettings.MTWin.get_data()
|
| 892 |
-
# GlobalSettings.pop_Analysis.get_data()
|
| 893 |
-
# self.getData()
|
| 894 |
-
# self.fill_annotation_dropdown()
|
| 895 |
-
# except Exception as e:
|
| 896 |
-
# show_error("Error in change_directory() in main.", e)
|
| 897 |
-
|
| 898 |
-
# def changeto_multitargeting(self):
|
| 899 |
-
# try:
|
| 900 |
-
# os.chdir(os.getcwd())
|
| 901 |
-
# if GlobalSettings.MTWin.first_show == True:
|
| 902 |
-
# GlobalSettings.MTWin.show()
|
| 903 |
-
# GlobalSettings.MTWin.first_show = False
|
| 904 |
-
# else:
|
| 905 |
-
# GlobalSettings.MTWin.show()
|
| 906 |
-
# GlobalSettings.mainWindow.hide()
|
| 907 |
-
|
| 908 |
-
# except Exception as e:
|
| 909 |
-
# show_error("Error in changeto_multitargeting() in main.", e)
|
| 910 |
-
|
| 911 |
-
# #change to population analysis window
|
| 912 |
-
# def changeto_population_Analysis(self):
|
| 913 |
-
# try:
|
| 914 |
-
# GlobalSettings.pop_Analysis.launch()
|
| 915 |
-
# if GlobalSettings.pop_Analysis.first_show == True:
|
| 916 |
-
# center_ui(GlobalSettings.pop_Analysis)
|
| 917 |
-
# GlobalSettings.pop_Analysis.first_show = False
|
| 918 |
-
# GlobalSettings.pop_Analysis.show()
|
| 919 |
-
# GlobalSettings.mainWindow.hide()
|
| 920 |
-
# except Exception as e:
|
| 921 |
-
# show_error("Error in changeto_population_Analysis() in main.", e)
|
| 922 |
-
|
| 923 |
-
# def annotation_information(self):
|
| 924 |
-
# try:
|
| 925 |
-
# show_message(
|
| 926 |
-
# fontSize=12,
|
| 927 |
-
# icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 928 |
-
# title="Annotation Information",
|
| 929 |
-
# message="Annotation files are used for searching for spacers on a gene/locus basis and can be selected here using either " \
|
| 930 |
-
# "NCBI databases or a local file."
|
| 931 |
-
# )
|
| 932 |
-
# except Exception as e:
|
| 933 |
-
# show_error("Error in annotation_information() in main.", e)
|
| 934 |
-
|
| 935 |
-
# @QtCore.pyqtSlot()
|
| 936 |
-
# def view_results(self):
|
| 937 |
-
# try:
|
| 938 |
-
# #center results window on current screen
|
| 939 |
-
# if self.Results.first_show == True:
|
| 940 |
-
# self.Results.first_show = False
|
| 941 |
-
# self.Results.centerUI()
|
| 942 |
-
|
| 943 |
-
# self.Results.show()
|
| 944 |
-
# self.hide()
|
| 945 |
-
# except Exception as e:
|
| 946 |
-
# show_error("Error in view_results() in main", e)
|
| 947 |
-
|
| 948 |
-
# def closeFunction(self):
|
| 949 |
-
# try:
|
| 950 |
-
# # Attempt to close the NCBI window if it exists
|
| 951 |
-
# try:
|
| 952 |
-
# self.ncbi.close()
|
| 953 |
-
# except AttributeError:
|
| 954 |
-
# print("No NCBI window to close.")
|
| 955 |
-
|
| 956 |
-
# self.myClosingWindow.get_files()
|
| 957 |
-
# center_ui(self.myClosingWindow)
|
| 958 |
-
# self.myClosingWindow.show()
|
| 959 |
-
# except Exception as e:
|
| 960 |
-
# show_error("Error in closeFunction() in main", e)
|
| 961 |
-
|
| 962 |
-
# def close_app(self):
|
| 963 |
-
# try:
|
| 964 |
-
# # Attempt to close the NCBI window if it exists
|
| 965 |
-
# try:
|
| 966 |
-
# self.ncbi.close()
|
| 967 |
-
# except Exception as e:
|
| 968 |
-
# print("No NCBI window to close.")
|
| 969 |
-
|
| 970 |
-
# self.closeFunction()
|
| 971 |
-
# self.close()
|
| 972 |
-
# except Exception as e:
|
| 973 |
-
# show_error("Error in close_app() in main", e)
|
| 974 |
-
|
| 975 |
-
# def load_dropdown_data(self):
|
| 976 |
-
# """Fill in organism/endo/annotation dropdown information."""
|
| 977 |
-
# try:
|
| 978 |
-
# self.getData()
|
| 979 |
-
# self.fill_annotation_dropdown()
|
| 980 |
-
# # self.logger.debug("Successfully loaded organism/endo/annotation drop down information in Main.")
|
| 981 |
-
# except Exception as e:
|
| 982 |
-
# show_error("Error in load_dropdown_data() in Main", e)
|
| 983 |
-
|
| 984 |
-
# # Call methods for other windows if needed
|
| 985 |
-
# # self.load_mt_data()
|
| 986 |
-
# # self.load_pop_analysis_data()
|
| 987 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,7 +1,7 @@
|
|
| 1 |
from PyQt6 import QtWidgets
|
| 2 |
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QTableWidget, QTableWidgetItem, QPushButton, QHBoxLayout, QLabel
|
| 3 |
from PyQt6 import uic
|
| 4 |
-
from PyQt6.QtCore import Qt
|
| 5 |
|
| 6 |
class FindTargetsView(QtWidgets.QMainWindow):
|
| 7 |
def __init__(self, global_settings):
|
|
@@ -14,6 +14,11 @@ class FindTargetsView(QtWidgets.QMainWindow):
|
|
| 14 |
self.results_table = self.findChild(QTableWidget, 'tblTargets')
|
| 15 |
self.results_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
# Set up the table columns
|
| 18 |
self.results_table.setColumnCount(7)
|
| 19 |
self.results_table.setHorizontalHeaderLabels([
|
|
@@ -23,50 +28,68 @@ class FindTargetsView(QtWidgets.QMainWindow):
|
|
| 23 |
|
| 24 |
self.push_button_view_targets = self.findChild(QPushButton, 'pbtnViewTargets')
|
| 25 |
|
| 26 |
-
#
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
# self.result_count_label = QLabel("Results: 0")
|
| 37 |
-
|
| 38 |
-
# main_layout = QVBoxLayout(self)
|
| 39 |
-
# main_layout.addWidget(self.result_count_label)
|
| 40 |
-
# main_layout.addWidget(self.results_table)
|
| 41 |
-
# main_layout.addLayout(button_layout)
|
| 42 |
|
| 43 |
def display_results(self, results):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
self.results_table.setRowCount(len(results))
|
| 45 |
|
|
|
|
| 46 |
for row, result in enumerate(results):
|
| 47 |
-
self.results_table.setItem(row, 0,
|
| 48 |
-
self.results_table.setItem(row, 1,
|
| 49 |
-
self.results_table.setItem(row, 2,
|
| 50 |
-
self.results_table.setItem(row, 3,
|
| 51 |
-
self.results_table.setItem(row, 4,
|
| 52 |
-
self.results_table.setItem(row, 5,
|
| 53 |
-
self.results_table.setItem(row, 6,
|
|
|
|
|
|
|
|
|
|
| 54 |
|
|
|
|
|
|
|
| 55 |
self.results_table.resizeColumnsToContents()
|
| 56 |
-
|
|
|
|
| 57 |
|
| 58 |
def get_selected_targets(self):
|
| 59 |
selected_rows = set(index.row() for index in self.results_table.selectedIndexes())
|
| 60 |
selected_targets = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
for row in selected_rows:
|
| 62 |
target = {
|
| 63 |
-
'feature_type': self.results_table.item(row,
|
| 64 |
-
'chromosome': self.results_table.item(row,
|
| 65 |
-
'feature_id': self.results_table.item(row,
|
| 66 |
-
'feature_name': self.results_table.item(row,
|
| 67 |
-
'feature_description': self.results_table.item(row,
|
| 68 |
-
'location': self.results_table.item(row,
|
| 69 |
-
'strand': self.results_table.item(row,
|
| 70 |
}
|
| 71 |
selected_targets.append(target)
|
| 72 |
return selected_targets
|
|
|
|
| 1 |
from PyQt6 import QtWidgets
|
| 2 |
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QTableWidget, QTableWidgetItem, QPushButton, QHBoxLayout, QLabel
|
| 3 |
from PyQt6 import uic
|
| 4 |
+
from PyQt6.QtCore import Qt, QTimer
|
| 5 |
|
| 6 |
class FindTargetsView(QtWidgets.QMainWindow):
|
| 7 |
def __init__(self, global_settings):
|
|
|
|
| 14 |
self.results_table = self.findChild(QTableWidget, 'tblTargets')
|
| 15 |
self.results_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
|
| 16 |
|
| 17 |
+
# Optimize table performance
|
| 18 |
+
self.results_table.setUpdatesEnabled(False) # Disable updates during setup
|
| 19 |
+
self.results_table.setSortingEnabled(False) # Disable sorting during setup
|
| 20 |
+
self.results_table.horizontalHeader().setStretchLastSection(True)
|
| 21 |
+
|
| 22 |
# Set up the table columns
|
| 23 |
self.results_table.setColumnCount(7)
|
| 24 |
self.results_table.setHorizontalHeaderLabels([
|
|
|
|
| 28 |
|
| 29 |
self.push_button_view_targets = self.findChild(QPushButton, 'pbtnViewTargets')
|
| 30 |
|
| 31 |
+
# Pre-allocate items for better performance
|
| 32 |
+
self._cached_items = {}
|
| 33 |
+
|
| 34 |
+
def _get_table_item(self, text):
|
| 35 |
+
"""Cache and reuse QTableWidgetItems for better performance"""
|
| 36 |
+
if text not in self._cached_items:
|
| 37 |
+
item = QTableWidgetItem(str(text))
|
| 38 |
+
item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable) # Make item read-only
|
| 39 |
+
self._cached_items[text] = item
|
| 40 |
+
return self._cached_items[text].clone()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
def display_results(self, results):
|
| 43 |
+
# Disable updates for bulk operations
|
| 44 |
+
self.results_table.setUpdatesEnabled(False)
|
| 45 |
+
self.results_table.setSortingEnabled(False)
|
| 46 |
+
|
| 47 |
+
# Set row count once
|
| 48 |
self.results_table.setRowCount(len(results))
|
| 49 |
|
| 50 |
+
# Batch insert items
|
| 51 |
for row, result in enumerate(results):
|
| 52 |
+
self.results_table.setItem(row, 0, self._get_table_item(result['feature_type']))
|
| 53 |
+
self.results_table.setItem(row, 1, self._get_table_item(str(result['chromosome'])))
|
| 54 |
+
self.results_table.setItem(row, 2, self._get_table_item(result['feature_id']))
|
| 55 |
+
self.results_table.setItem(row, 3, self._get_table_item(result['feature_name']))
|
| 56 |
+
self.results_table.setItem(row, 4, self._get_table_item(result['feature_description']))
|
| 57 |
+
self.results_table.setItem(row, 5, self._get_table_item(result['location']))
|
| 58 |
+
self.results_table.setItem(row, 6, self._get_table_item(result['strand']))
|
| 59 |
+
|
| 60 |
+
# Re-enable updates and adjust columns
|
| 61 |
+
QTimer.singleShot(0, self._finish_table_update)
|
| 62 |
|
| 63 |
+
def _finish_table_update(self):
|
| 64 |
+
"""Complete table update in the next event loop iteration"""
|
| 65 |
self.results_table.resizeColumnsToContents()
|
| 66 |
+
self.results_table.setUpdatesEnabled(True)
|
| 67 |
+
self.results_table.setSortingEnabled(True)
|
| 68 |
|
| 69 |
def get_selected_targets(self):
|
| 70 |
selected_rows = set(index.row() for index in self.results_table.selectedIndexes())
|
| 71 |
selected_targets = []
|
| 72 |
+
|
| 73 |
+
# Get column indices once
|
| 74 |
+
columns = {
|
| 75 |
+
'feature_type': 0,
|
| 76 |
+
'chromosome': 1,
|
| 77 |
+
'feature_id': 2,
|
| 78 |
+
'feature_name': 3,
|
| 79 |
+
'feature_description': 4,
|
| 80 |
+
'location': 5,
|
| 81 |
+
'strand': 6
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
for row in selected_rows:
|
| 85 |
target = {
|
| 86 |
+
'feature_type': self.results_table.item(row, columns['feature_type']).text(),
|
| 87 |
+
'chromosome': self.results_table.item(row, columns['chromosome']).text(),
|
| 88 |
+
'feature_id': self.results_table.item(row, columns['feature_id']).text(),
|
| 89 |
+
'feature_name': self.results_table.item(row, columns['feature_name']).text(),
|
| 90 |
+
'feature_description': self.results_table.item(row, columns['feature_description']).text(),
|
| 91 |
+
'location': self.results_table.item(row, columns['location']).text(),
|
| 92 |
+
'strand': self.results_table.item(row, columns['strand']).text()
|
| 93 |
}
|
| 94 |
selected_targets.append(target)
|
| 95 |
return selected_targets
|
|
@@ -124,3 +124,6 @@ class HomeWindowView(QWidget):
|
|
| 124 |
return "sequence"
|
| 125 |
else:
|
| 126 |
return "feature" # Default to feature if somehow none are selected
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
return "sequence"
|
| 125 |
else:
|
| 126 |
return "feature" # Default to feature if somehow none are selected
|
| 127 |
+
|
| 128 |
+
def get_annotation_file(self) -> str:
|
| 129 |
+
return self.combo_box_local_annotation_files.currentText()
|
|
@@ -13,53 +13,149 @@ from functools import partial
|
|
| 13 |
import qdarktheme
|
| 14 |
|
| 15 |
class CloseableTabWidget(QTabWidget):
|
| 16 |
-
# Define a new signal that emits the closed widget
|
| 17 |
tab_closed = pyqtSignal(QWidget)
|
| 18 |
|
| 19 |
def __init__(self, parent=None):
|
| 20 |
super().__init__(parent)
|
| 21 |
self.setTabsClosable(False)
|
| 22 |
self.tabCloseRequested.connect(self.closeTab)
|
|
|
|
|
|
|
| 23 |
|
| 24 |
def closeTab(self, index):
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
def addTab(self, widget, label):
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
close_button.setAutoRaise(True)
|
| 42 |
-
|
| 43 |
-
# Apply updated stylesheet with adjusted negative margin and border-radius
|
| 44 |
-
close_button.setStyleSheet("""
|
| 45 |
-
QToolButton {
|
| 46 |
-
border: none;
|
| 47 |
-
padding: 0px;
|
| 48 |
-
}
|
| 49 |
-
QToolButton:hover {
|
| 50 |
-
background: #c42b1c;
|
| 51 |
}
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
|
| 65 |
class MainWindowView(QMainWindow):
|
|
@@ -71,13 +167,38 @@ class MainWindowView(QMainWindow):
|
|
| 71 |
self.oldPos = None
|
| 72 |
|
| 73 |
def _init_ui(self) -> None:
|
|
|
|
|
|
|
|
|
|
| 74 |
try:
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
self._load_ui_file()
|
| 77 |
self._init_window_properties()
|
| 78 |
self._init_ui_elements()
|
|
|
|
| 79 |
self._scale_ui()
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
except Exception as e:
|
| 82 |
self._handle_init_error(e)
|
| 83 |
|
|
@@ -89,11 +210,16 @@ class MainWindowView(QMainWindow):
|
|
| 89 |
"""
|
| 90 |
Creates a frameless, translucent window without a toolbar.
|
| 91 |
"""
|
|
|
|
| 92 |
self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
|
| 93 |
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
|
|
|
|
|
|
| 94 |
toolbars = self.findChildren(QtWidgets.QToolBar)
|
| 95 |
for toolbar in toolbars:
|
| 96 |
toolbar.hide()
|
|
|
|
|
|
|
| 97 |
|
| 98 |
def _init_ui_elements(self) -> None:
|
| 99 |
# Initialize menu bar and custom title bar
|
|
@@ -260,20 +386,40 @@ class MainWindowView(QMainWindow):
|
|
| 260 |
# self.tab_widget.setCurrentIndex(tab_index)
|
| 261 |
|
| 262 |
|
| 263 |
-
def _scale_ui(self)
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
def _handle_init_error(self, e: Exception) -> None:
|
| 279 |
error_msg = f"Error initializing MainWindowView: {str(e)}"
|
|
@@ -395,3 +541,14 @@ class MainWindowView(QMainWindow):
|
|
| 395 |
background: {theme['tab_hover_bg_color']};
|
| 396 |
}}
|
| 397 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
import qdarktheme
|
| 14 |
|
| 15 |
class CloseableTabWidget(QTabWidget):
|
|
|
|
| 16 |
tab_closed = pyqtSignal(QWidget)
|
| 17 |
|
| 18 |
def __init__(self, parent=None):
|
| 19 |
super().__init__(parent)
|
| 20 |
self.setTabsClosable(False)
|
| 21 |
self.tabCloseRequested.connect(self.closeTab)
|
| 22 |
+
self._tabs = {} # Dictionary to keep track of tab widgets
|
| 23 |
+
self.tabBar().tabMoved.connect(self._handle_tab_moved)
|
| 24 |
|
| 25 |
def closeTab(self, index):
|
| 26 |
+
try:
|
| 27 |
+
if self.count() > 1 and index != 0:
|
| 28 |
+
widget = self.widget(index)
|
| 29 |
+
if widget:
|
| 30 |
+
# Get tab text before removal
|
| 31 |
+
tab_text = self.tabText(index)
|
| 32 |
+
|
| 33 |
+
# Clean up the controller if it exists
|
| 34 |
+
controller = getattr(widget, 'controller', None)
|
| 35 |
+
if controller and hasattr(controller, 'model') and hasattr(controller.model, 'cleanup'):
|
| 36 |
+
controller.model.cleanup()
|
| 37 |
+
|
| 38 |
+
# Remove from tracking dictionary
|
| 39 |
+
if tab_text in self._tabs:
|
| 40 |
+
del self._tabs[tab_text]
|
| 41 |
+
|
| 42 |
+
# Remove the tab
|
| 43 |
+
self.removeTab(index)
|
| 44 |
+
|
| 45 |
+
# Emit signal before deletion
|
| 46 |
+
self.tab_closed.emit(widget)
|
| 47 |
+
|
| 48 |
+
# Schedule widget for deletion
|
| 49 |
+
widget.deleteLater()
|
| 50 |
+
|
| 51 |
+
# Update all remaining tabs
|
| 52 |
+
self._update_all_tabs()
|
| 53 |
+
except Exception as e:
|
| 54 |
+
print(f"Error closing tab: {e}")
|
| 55 |
|
| 56 |
def addTab(self, widget, label):
|
| 57 |
+
try:
|
| 58 |
+
if widget and label:
|
| 59 |
+
# Store widget reference with unique identifier
|
| 60 |
+
tab_id = f"{label}_{id(widget)}"
|
| 61 |
+
self._tabs[tab_id] = {
|
| 62 |
+
'widget': widget,
|
| 63 |
+
'label': label,
|
| 64 |
+
'close_button': None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
}
|
| 66 |
+
|
| 67 |
+
# Add the tab
|
| 68 |
+
index = super().addTab(widget, label)
|
| 69 |
+
|
| 70 |
+
if index != 0:
|
| 71 |
+
# Create and setup close button
|
| 72 |
+
close_button = self._create_close_button(index, label)
|
| 73 |
+
self._tabs[tab_id]['close_button'] = close_button
|
| 74 |
+
self.tabBar().setTabButton(index, QTabBar.ButtonPosition.RightSide, close_button)
|
| 75 |
+
|
| 76 |
+
return index
|
| 77 |
+
except Exception as e:
|
| 78 |
+
print(f"Error adding tab: {e}")
|
| 79 |
+
return -1
|
| 80 |
+
|
| 81 |
+
def _create_close_button(self, index, label):
|
| 82 |
+
"""Create a new close button for a tab"""
|
| 83 |
+
close_button = QToolButton(self.tabBar())
|
| 84 |
+
close_button.setObjectName(f"close_button_{label}")
|
| 85 |
+
close_icon = self.style().standardIcon(QtWidgets.QStyle.StandardPixmap.SP_TitleBarCloseButton)
|
| 86 |
+
close_button.setIcon(close_icon)
|
| 87 |
+
close_button.setIconSize(QSize(16, 16))
|
| 88 |
+
close_button.setAutoRaise(True)
|
| 89 |
+
close_button.setStyleSheet("""
|
| 90 |
+
QToolButton {
|
| 91 |
+
border: none;
|
| 92 |
+
padding: 0px;
|
| 93 |
+
}
|
| 94 |
+
QToolButton:hover {
|
| 95 |
+
background: #c42b1c;
|
| 96 |
+
}
|
| 97 |
+
""")
|
| 98 |
+
close_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
|
| 99 |
+
close_button.setFixedSize(18, 18)
|
| 100 |
+
close_button.clicked.connect(lambda checked, idx=index: self.safely_close_tab(idx))
|
| 101 |
+
return close_button
|
| 102 |
|
| 103 |
+
def safely_close_tab(self, index):
|
| 104 |
+
"""Safely handle tab closure with error checking"""
|
| 105 |
+
try:
|
| 106 |
+
if 0 <= index < self.count():
|
| 107 |
+
current_widget = self.widget(index)
|
| 108 |
+
if current_widget and index != 0:
|
| 109 |
+
self.closeTab(index)
|
| 110 |
+
except Exception as e:
|
| 111 |
+
print(f"Error in safely_close_tab: {e}")
|
| 112 |
+
|
| 113 |
+
def _handle_tab_moved(self, from_index: int, to_index: int):
|
| 114 |
+
"""Handle tab movement and update close buttons"""
|
| 115 |
+
try:
|
| 116 |
+
self._update_all_tabs()
|
| 117 |
+
except Exception as e:
|
| 118 |
+
print(f"Error handling tab movement: {e}")
|
| 119 |
+
|
| 120 |
+
def _update_all_tabs(self):
|
| 121 |
+
"""Update all tabs and their close buttons"""
|
| 122 |
+
try:
|
| 123 |
+
for i in range(1, self.count()): # Skip index 0 (home tab)
|
| 124 |
+
widget = self.widget(i)
|
| 125 |
+
if widget:
|
| 126 |
+
label = self.tabText(i)
|
| 127 |
+
tab_id = f"{label}_{id(widget)}"
|
| 128 |
+
|
| 129 |
+
# Create new close button if needed
|
| 130 |
+
if tab_id not in self._tabs or not self._tabs[tab_id].get('close_button'):
|
| 131 |
+
close_button = self._create_close_button(i, label)
|
| 132 |
+
self._tabs[tab_id] = {
|
| 133 |
+
'widget': widget,
|
| 134 |
+
'label': label,
|
| 135 |
+
'close_button': close_button
|
| 136 |
+
}
|
| 137 |
+
self.tabBar().setTabButton(i, QTabBar.ButtonPosition.RightSide, close_button)
|
| 138 |
+
else:
|
| 139 |
+
# Update existing close button's click connection
|
| 140 |
+
close_button = self._tabs[tab_id]['close_button']
|
| 141 |
+
close_button.clicked.disconnect()
|
| 142 |
+
close_button.clicked.connect(lambda checked, idx=i: self.safely_close_tab(idx))
|
| 143 |
+
except Exception as e:
|
| 144 |
+
print(f"Error updating tabs: {e}")
|
| 145 |
+
|
| 146 |
+
def moveTab(self, from_index, to_index):
|
| 147 |
+
"""Override moveTab to safely handle tab movement"""
|
| 148 |
+
try:
|
| 149 |
+
if (0 <= from_index < self.count() and
|
| 150 |
+
0 <= to_index < self.count() and
|
| 151 |
+
from_index != 0 and
|
| 152 |
+
to_index != 0):
|
| 153 |
+
|
| 154 |
+
super().moveTab(from_index, to_index)
|
| 155 |
+
self._update_all_tabs()
|
| 156 |
+
|
| 157 |
+
except Exception as e:
|
| 158 |
+
print(f"Error moving tab: {e}")
|
| 159 |
|
| 160 |
|
| 161 |
class MainWindowView(QMainWindow):
|
|
|
|
| 167 |
self.oldPos = None
|
| 168 |
|
| 169 |
def _init_ui(self) -> None:
|
| 170 |
+
# Hide the window and disable updates during initialization
|
| 171 |
+
self.hide()
|
| 172 |
+
self.setUpdatesEnabled(False)
|
| 173 |
try:
|
| 174 |
+
# Calculate center position first
|
| 175 |
+
screen = QtGui.QGuiApplication.primaryScreen()
|
| 176 |
+
screen_geometry = screen.geometry()
|
| 177 |
+
centerPoint = screen_geometry.center()
|
| 178 |
+
|
| 179 |
+
# Load and initialize UI
|
| 180 |
self._load_ui_file()
|
| 181 |
self._init_window_properties()
|
| 182 |
self._init_ui_elements()
|
| 183 |
+
self.apply_theme()
|
| 184 |
self._scale_ui()
|
| 185 |
+
|
| 186 |
+
# Get final size
|
| 187 |
+
final_size = self.size()
|
| 188 |
+
|
| 189 |
+
# Calculate position only once
|
| 190 |
+
x = centerPoint.x() - (final_size.width() // 2)
|
| 191 |
+
y = centerPoint.y() - (final_size.height() // 2)
|
| 192 |
+
|
| 193 |
+
# Set position and size in a single operation
|
| 194 |
+
self.setGeometry(x, y, final_size.width(), final_size.height())
|
| 195 |
+
|
| 196 |
+
# Re-enable updates and show window
|
| 197 |
+
self.setUpdatesEnabled(True)
|
| 198 |
+
self.show()
|
| 199 |
+
self.repaint() # Force immediate repaint
|
| 200 |
+
|
| 201 |
+
self.logger.debug(f"Window initialized at position ({x}, {y}) with size {final_size}")
|
| 202 |
except Exception as e:
|
| 203 |
self._handle_init_error(e)
|
| 204 |
|
|
|
|
| 210 |
"""
|
| 211 |
Creates a frameless, translucent window without a toolbar.
|
| 212 |
"""
|
| 213 |
+
# Set window flags before other properties
|
| 214 |
self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
|
| 215 |
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
| 216 |
+
self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
|
| 217 |
+
# Hide toolbars
|
| 218 |
toolbars = self.findChildren(QtWidgets.QToolBar)
|
| 219 |
for toolbar in toolbars:
|
| 220 |
toolbar.hide()
|
| 221 |
+
# Ensure window starts hidden
|
| 222 |
+
self.setVisible(False)
|
| 223 |
|
| 224 |
def _init_ui_elements(self) -> None:
|
| 225 |
# Initialize menu bar and custom title bar
|
|
|
|
| 386 |
# self.tab_widget.setCurrentIndex(tab_index)
|
| 387 |
|
| 388 |
|
| 389 |
+
def _scale_ui(self):
|
| 390 |
+
"""Modified scale_ui to only handle sizing, not positioning"""
|
| 391 |
+
try:
|
| 392 |
+
screen = QtGui.QGuiApplication.primaryScreen()
|
| 393 |
+
screen_geometry = screen.geometry()
|
| 394 |
+
width = screen_geometry.width()
|
| 395 |
+
height = screen_geometry.height()
|
| 396 |
+
|
| 397 |
+
# Font scaling
|
| 398 |
+
self.centralWidget().setStyleSheet(f"font: 12pt 'Arial';")
|
| 399 |
+
|
| 400 |
+
if hasattr(self, 'title'):
|
| 401 |
+
scaled_title_font_size = int(30 * (width / 1920))
|
| 402 |
+
self.title.setStyleSheet(f"font: bold {scaled_title_font_size}pt 'Arial';")
|
| 403 |
+
|
| 404 |
+
# Calculate size only
|
| 405 |
+
scaledWidth = int((width * 575) / 1920)
|
| 406 |
+
scaledHeight = int((height * 400) / 1080)
|
| 407 |
+
|
| 408 |
+
# Ensure minimum size
|
| 409 |
+
self.adjustSize()
|
| 410 |
+
currentWidth = self.size().width()
|
| 411 |
+
currentHeight = self.size().height()
|
| 412 |
+
|
| 413 |
+
if scaledHeight < currentHeight:
|
| 414 |
+
scaledHeight = currentHeight
|
| 415 |
+
if scaledWidth < currentWidth:
|
| 416 |
+
scaledWidth = currentWidth
|
| 417 |
+
|
| 418 |
+
# Only resize, don't reposition
|
| 419 |
+
self.resize(scaledWidth, scaledHeight)
|
| 420 |
+
|
| 421 |
+
except Exception as e:
|
| 422 |
+
self.logger.error(f"Error in _scale_ui: {str(e)}")
|
| 423 |
|
| 424 |
def _handle_init_error(self, e: Exception) -> None:
|
| 425 |
error_msg = f"Error initializing MainWindowView: {str(e)}"
|
|
|
|
| 541 |
background: {theme['tab_hover_bg_color']};
|
| 542 |
}}
|
| 543 |
""")
|
| 544 |
+
|
| 545 |
+
def show_window(self) -> None:
|
| 546 |
+
"""Shows the window without repositioning"""
|
| 547 |
+
self.show()
|
| 548 |
+
self.repaint()
|
| 549 |
+
|
| 550 |
+
|
| 551 |
+
|
| 552 |
+
|
| 553 |
+
|
| 554 |
+
|
|
@@ -1,108 +1,370 @@
|
|
| 1 |
-
from
|
| 2 |
-
from PyQt6
|
|
|
|
| 3 |
from PyQt6.QtGui import QIcon
|
| 4 |
-
from PyQt6 import
|
| 5 |
-
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
|
| 6 |
from matplotlib.figure import Figure
|
| 7 |
-
import models.GlobalSettings as GlobalSettings
|
| 8 |
from matplotlib.ticker import MaxNLocator
|
| 9 |
-
from
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
import os
|
| 14 |
-
|
| 15 |
-
class MultitargetingWindowView(QWidget):
|
| 16 |
-
def __init__(self, settings):
|
| 17 |
super().__init__()
|
| 18 |
-
self.settings =
|
| 19 |
-
self.
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
def
|
| 22 |
try:
|
| 23 |
-
self.
|
| 24 |
-
|
| 25 |
-
# self._scale_ui()
|
| 26 |
except Exception as e:
|
| 27 |
-
self.
|
| 28 |
|
| 29 |
-
def
|
| 30 |
-
|
| 31 |
-
self.
|
| 32 |
-
|
| 33 |
-
raise
|
| 34 |
|
| 35 |
-
def
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
-
def
|
| 44 |
-
#
|
| 45 |
-
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
pass
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
-
|
|
|
|
| 64 |
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
try:
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
except Exception as e:
|
| 73 |
-
|
| 74 |
|
| 75 |
-
|
| 76 |
-
|
| 77 |
try:
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
except Exception as e:
|
| 85 |
-
|
| 86 |
|
| 87 |
-
|
| 88 |
-
|
| 89 |
try:
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
except Exception as e:
|
| 96 |
-
|
| 97 |
|
| 98 |
-
|
| 99 |
-
|
| 100 |
try:
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
except Exception as e:
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
from PyQt6 import QtWidgets, uic, QtGui
|
| 3 |
+
from PyQt6.QtWidgets import QTableWidgetItem, QAbstractItemView
|
| 4 |
from PyQt6.QtGui import QIcon
|
| 5 |
+
from PyQt6.QtCore import Qt
|
| 6 |
+
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT
|
| 7 |
from matplotlib.figure import Figure
|
|
|
|
| 8 |
from matplotlib.ticker import MaxNLocator
|
| 9 |
+
from utils.ui import show_error, scale_ui
|
| 10 |
+
|
| 11 |
+
class MultitargetingWindowView(QtWidgets.QMainWindow):
|
| 12 |
+
def __init__(self, global_settings):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
super().__init__()
|
| 14 |
+
self.settings = global_settings
|
| 15 |
+
self.logger = self.settings.get_logger()
|
| 16 |
+
|
| 17 |
+
self.init_ui()
|
| 18 |
|
| 19 |
+
def init_ui(self):
|
| 20 |
try:
|
| 21 |
+
uic.loadUi(self.settings.get_ui_dir_path() + '/multitargeting_window.ui', self)
|
| 22 |
+
self._init_ui_components()
|
|
|
|
| 23 |
except Exception as e:
|
| 24 |
+
show_error(self.settings, "Error initializing MultitargetingWindowView", str(e))
|
| 25 |
|
| 26 |
+
def _init_ui_components(self):
|
| 27 |
+
self._init_grpSelectOrganism()
|
| 28 |
+
self._init_grpSeedAnalysis()
|
| 29 |
+
self._init_grpGlobalAnalysis()
|
|
|
|
| 30 |
|
| 31 |
+
def _init_grpSelectOrganism(self):
|
| 32 |
+
self.combo_box_organism = self._find_widget('cmbOrganism', QtWidgets.QComboBox)
|
| 33 |
+
self.combo_box_endonuclease = self._find_widget('cmbEndonuclease', QtWidgets.QComboBox)
|
| 34 |
+
self.push_button_analyze = self._find_widget('pbtnAnalyze', QtWidgets.QPushButton)
|
| 35 |
+
self.check_box_select_all = self._find_widget('chkSelectAll', QtWidgets.QCheckBox)
|
| 36 |
+
self.tool_button_sql_settings = self._find_widget('tbtnSQLSettings', QtWidgets.QToolButton)
|
| 37 |
+
self.table_seeds = self._find_widget('tblSeeds', QtWidgets.QTableWidget)
|
| 38 |
|
| 39 |
+
self.table_seeds.setColumnCount(8)
|
| 40 |
+
self.table_seeds.setHorizontalHeaderLabels(["Seed", "Total Repeats", "Avg. Repeats/Scaffold", "Consensus Sequence", "% Consensus", "Score", "PAM", "Strand"])
|
| 41 |
+
self.table_seeds.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
| 42 |
+
self.table_seeds.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
| 43 |
+
self.table_seeds.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
| 44 |
+
self.table_seeds.horizontalHeader().setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeMode.Stretch)
|
| 45 |
|
| 46 |
+
def _init_grpSeedAnalysis(self):
|
| 47 |
+
# Get the tab widget
|
| 48 |
+
self.tab_widget_seed_analysis = self._find_widget('tabsSeedAnalysis', QtWidgets.QTabWidget)
|
| 49 |
|
| 50 |
+
# Get screen height for scaling
|
| 51 |
+
screen = self.screen()
|
| 52 |
+
height = screen.geometry().height()
|
| 53 |
+
# Set tab widget to take up less vertical space (e.g., 30% of screen height)
|
| 54 |
+
tab_height = int((height * 30) / 100)
|
| 55 |
+
self.tab_widget_seed_analysis.setMinimumHeight(tab_height)
|
| 56 |
+
self.tab_widget_seed_analysis.setMaximumHeight(tab_height)
|
| 57 |
|
| 58 |
+
# Initialize other widgets
|
| 59 |
+
self.tab_chromosome_viewer = self._find_widget('tabChromosomeViewer', QtWidgets.QWidget)
|
| 60 |
+
self.graphical_view_chromosome = self._find_widget('graphviewChromosome', QtWidgets.QGraphicsView)
|
| 61 |
+
self.scroll_chromosome = self._find_widget('scrollChromosome', QtWidgets.QScrollArea)
|
| 62 |
+
|
| 63 |
+
# Create a widget to hold the chromosome visualizations
|
| 64 |
+
self.chromosome_content_widget = QtWidgets.QWidget()
|
| 65 |
+
self.chromosome_layout = QtWidgets.QVBoxLayout(self.chromosome_content_widget)
|
| 66 |
+
self.scroll_chromosome.setWidget(self.chromosome_content_widget)
|
| 67 |
+
self.scroll_chromosome.setWidgetResizable(True)
|
| 68 |
|
| 69 |
+
self.tab_seed_distribution = self._find_widget('tabSeedDistribution', QtWidgets.QWidget)
|
| 70 |
+
self.plot_repeat_vs_chromosome = self._find_widget('plotRepeatVsChromosome', QtWidgets.QWidget)
|
|
|
|
| 71 |
|
| 72 |
+
# Initialize scene for chromosome details
|
| 73 |
+
self.scene = QtWidgets.QGraphicsScene()
|
| 74 |
+
self.scene2 = QtWidgets.QGraphicsScene()
|
| 75 |
+
self.graphical_view_chromosome.setScene(self.scene2)
|
| 76 |
+
|
| 77 |
+
# Set up event filters
|
| 78 |
+
self.scroll_chromosome.viewport().installEventFilter(self)
|
| 79 |
+
self.graphical_view_chromosome.viewport().installEventFilter(self)
|
| 80 |
|
| 81 |
+
# Dictionary to map canvases to chromosomes
|
| 82 |
+
self.canvas_chromosome_map = {}
|
| 83 |
|
| 84 |
+
def _init_grpGlobalAnalysis(self):
|
| 85 |
+
self.push_button_statistics_overview = self._find_widget('pbtnStatisticsOverview', QtWidgets.QPushButton)
|
| 86 |
+
|
| 87 |
+
self.tab_repeats_vs_seed = self._find_widget('tabRepeatsVsSeed', QtWidgets.QWidget)
|
| 88 |
+
self.plot_repeats_vs_seed = self._find_widget('plotRepeatsVsSeed', QtWidgets.QWidget)
|
| 89 |
+
|
| 90 |
+
self.tab_sequences_vs_repeats = self._find_widget('tabSequencesVsRepeats', QtWidgets.QWidget)
|
| 91 |
+
self.plot_sequences_vs_repeats = self._find_widget('plotSequencesVsRepeats', QtWidgets.QWidget)
|
| 92 |
+
|
| 93 |
+
def _find_widget(self, name: str, widget_type: type) -> Optional[QtWidgets.QWidget]:
|
| 94 |
+
widget = self.findChild(widget_type, name)
|
| 95 |
+
if widget is None:
|
| 96 |
+
self.logger.warning(f"Widget '{name}' not found in UI file.")
|
| 97 |
+
return widget
|
| 98 |
+
|
| 99 |
+
def update_seeds_table(self, data):
|
| 100 |
+
"""Update the seeds table with the provided data"""
|
| 101 |
+
self.table_seeds.setRowCount(len(data))
|
| 102 |
+
for row, row_data in enumerate(data):
|
| 103 |
+
# Unpack the data
|
| 104 |
+
(seed, total_count, avg_per_scaffold, sequences,
|
| 105 |
+
consensus_percent, score, pam, strand) = row_data
|
| 106 |
+
|
| 107 |
+
# Set data in table cells
|
| 108 |
+
self.table_seeds.setItem(row, 0, QTableWidgetItem(str(seed)))
|
| 109 |
+
self.table_seeds.setItem(row, 1, QTableWidgetItem(str(total_count)))
|
| 110 |
+
self.table_seeds.setItem(row, 2, QTableWidgetItem(f"{avg_per_scaffold:.2f}"))
|
| 111 |
+
self.table_seeds.setItem(row, 3, QTableWidgetItem(sequences.split(',')[0])) # First sequence
|
| 112 |
+
self.table_seeds.setItem(row, 4, QTableWidgetItem(f"{consensus_percent:.1f}"))
|
| 113 |
+
self.table_seeds.setItem(row, 5, QTableWidgetItem(str(score)))
|
| 114 |
+
self.table_seeds.setItem(row, 6, QTableWidgetItem(str(pam)))
|
| 115 |
+
self.table_seeds.setItem(row, 7, QTableWidgetItem(str(strand)))
|
| 116 |
+
|
| 117 |
+
# Set alignment for all cells
|
| 118 |
+
for col in range(8):
|
| 119 |
+
item = self.table_seeds.item(row, col)
|
| 120 |
+
if item:
|
| 121 |
+
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
| 122 |
+
|
| 123 |
+
self.table_seeds.resizeColumnsToContents()
|
| 124 |
+
|
| 125 |
+
def setup_plots(self):
|
| 126 |
+
"""Initialize the matplotlib plots"""
|
| 127 |
+
self.repeats_vs_seed_canvas = MplCanvas(self, width=8, height=6)
|
| 128 |
+
self.sequences_vs_repeats_canvas = MplCanvas(self, width=8, height=6)
|
| 129 |
+
self.repeat_vs_chromosome_canvas = MplCanvas(self, width=8, height=6)
|
| 130 |
+
|
| 131 |
+
# Add canvases to their respective layouts without toolbars
|
| 132 |
+
for plot_widget, canvas in [
|
| 133 |
+
(self.plot_repeats_vs_seed, self.repeats_vs_seed_canvas),
|
| 134 |
+
(self.plot_sequences_vs_repeats, self.sequences_vs_repeats_canvas),
|
| 135 |
+
(self.plot_repeat_vs_chromosome, self.repeat_vs_chromosome_canvas)
|
| 136 |
+
]:
|
| 137 |
+
layout = QtWidgets.QVBoxLayout(plot_widget)
|
| 138 |
+
layout.setContentsMargins(0, 0, 0, 0) # Reduce margins
|
| 139 |
+
layout.addWidget(canvas)
|
| 140 |
+
|
| 141 |
+
def update_plots(self, repeats_data, sequences_data, chromosome_data):
|
| 142 |
+
"""Update all plots with new data"""
|
| 143 |
+
self._update_repeats_vs_seed_plot(repeats_data)
|
| 144 |
+
self._update_sequences_vs_repeats_plot(sequences_data)
|
| 145 |
+
self._update_repeat_vs_chromosome_plot(chromosome_data)
|
| 146 |
+
|
| 147 |
+
def _update_repeats_vs_seed_plot(self, data):
|
| 148 |
+
"""Update the repeats vs seed line plot"""
|
| 149 |
try:
|
| 150 |
+
self.logger.debug("Starting repeats vs seed plot update")
|
| 151 |
+
print(f"data: {data}")
|
| 152 |
+
|
| 153 |
+
self.repeats_vs_seed_canvas.axes.clear()
|
| 154 |
+
|
| 155 |
+
if data and 'counts' in data:
|
| 156 |
+
y1 = data['counts']
|
| 157 |
+
x = range(len(y1))
|
| 158 |
+
|
| 159 |
+
# Plot with larger markers and line width for better visibility
|
| 160 |
+
self.repeats_vs_seed_canvas.axes.plot(x, y1, linewidth=1.5, marker='.', markersize=3)
|
| 161 |
+
|
| 162 |
+
# Set labels and title with larger font sizes
|
| 163 |
+
self.repeats_vs_seed_canvas.axes.set_xlabel('Seed ID Number', fontsize=12)
|
| 164 |
+
self.repeats_vs_seed_canvas.axes.set_ylabel('Number of Repeats', fontsize=12)
|
| 165 |
+
self.repeats_vs_seed_canvas.axes.set_title('Number of Repeats per Seed ID Number', fontsize=14)
|
| 166 |
+
|
| 167 |
+
# Set tick label size
|
| 168 |
+
self.repeats_vs_seed_canvas.axes.tick_params(axis='both', which='major', labelsize=10)
|
| 169 |
+
|
| 170 |
+
# Add grid for better readability
|
| 171 |
+
self.repeats_vs_seed_canvas.axes.grid(True, linestyle='--', alpha=0.7)
|
| 172 |
+
|
| 173 |
+
# Store statistics if needed
|
| 174 |
+
if 'stats' in data:
|
| 175 |
+
self.average = data['stats']['average']
|
| 176 |
+
self.mode = data['stats']['mode']
|
| 177 |
+
self.median = data['stats']['median']
|
| 178 |
+
self.repeat_count = data['stats']['repeat_count']
|
| 179 |
+
|
| 180 |
+
# Force draw
|
| 181 |
+
self.repeats_vs_seed_canvas.draw()
|
| 182 |
+
|
| 183 |
+
else:
|
| 184 |
+
self.logger.warning("No valid data for plotting")
|
| 185 |
+
self.repeats_vs_seed_canvas.draw() # Still need to draw even when clearing
|
| 186 |
+
|
| 187 |
except Exception as e:
|
| 188 |
+
self.logger.error(f"Error updating repeats vs seed plot: {str(e)}")
|
| 189 |
|
| 190 |
+
def _update_sequences_vs_repeats_plot(self, data):
|
| 191 |
+
"""Update the sequences vs repeats plot"""
|
| 192 |
try:
|
| 193 |
+
self.sequences_vs_repeats_canvas.axes.clear()
|
| 194 |
+
|
| 195 |
+
if data and 'x_vals' in data and 'y_vals' in data:
|
| 196 |
+
x = data['x_vals']
|
| 197 |
+
y = data['y_vals']
|
| 198 |
+
|
| 199 |
+
# Create scatter plot with specific style
|
| 200 |
+
self.sequences_vs_repeats_canvas.axes.scatter(x, y, s=15, color='blue')
|
| 201 |
+
|
| 202 |
+
# Set y-axis to log scale
|
| 203 |
+
self.sequences_vs_repeats_canvas.axes.set_yscale('log')
|
| 204 |
+
|
| 205 |
+
# Set labels and title
|
| 206 |
+
self.sequences_vs_repeats_canvas.axes.set_xlabel('Number of Repeats', fontsize=12)
|
| 207 |
+
self.sequences_vs_repeats_canvas.axes.set_ylabel('Number of Sequences', fontsize=12)
|
| 208 |
+
self.sequences_vs_repeats_canvas.axes.set_title('Number of Sequences per Number of Repeats', fontsize=14)
|
| 209 |
+
|
| 210 |
+
# Set tick label size
|
| 211 |
+
self.sequences_vs_repeats_canvas.axes.tick_params(axis='both', which='major', labelsize=10)
|
| 212 |
+
|
| 213 |
+
# Add grid for better readability
|
| 214 |
+
self.sequences_vs_repeats_canvas.axes.grid(True, linestyle='--', alpha=0.7)
|
| 215 |
+
|
| 216 |
+
# Force integer ticks on x-axis
|
| 217 |
+
self.sequences_vs_repeats_canvas.axes.xaxis.set_major_locator(MaxNLocator(integer=True))
|
| 218 |
+
|
| 219 |
+
# Set axis ranges to match the image
|
| 220 |
+
self.sequences_vs_repeats_canvas.axes.set_xlim(0, max(x) + 5) # Add some padding
|
| 221 |
+
self.sequences_vs_repeats_canvas.axes.set_ylim(1, 10**4) # Log scale from 1 to 10^4
|
| 222 |
+
|
| 223 |
+
self.sequences_vs_repeats_canvas.draw()
|
| 224 |
+
|
| 225 |
except Exception as e:
|
| 226 |
+
self.logger.error(f"Error updating sequences vs repeats plot: {str(e)}")
|
| 227 |
|
| 228 |
+
def _update_repeat_vs_chromosome_plot(self, data):
|
| 229 |
+
"""Update the chromosome bar plot"""
|
| 230 |
try:
|
| 231 |
+
self.repeat_vs_chromosome_canvas.axes.clear()
|
| 232 |
+
|
| 233 |
+
if data:
|
| 234 |
+
y = []
|
| 235 |
+
x_labels = []
|
| 236 |
+
|
| 237 |
+
# Get sorted chromosome numbers and their counts
|
| 238 |
+
for chromo in sorted(data.keys()):
|
| 239 |
+
x_labels.append(chromo)
|
| 240 |
+
y.append(data[chromo])
|
| 241 |
+
|
| 242 |
+
x = list(range(0, len(x_labels)))
|
| 243 |
+
|
| 244 |
+
# Create the bar plot
|
| 245 |
+
self.repeat_vs_chromosome_canvas.axes.bar(x, y, align='center')
|
| 246 |
+
|
| 247 |
+
# Set integer y-axis
|
| 248 |
+
self.repeat_vs_chromosome_canvas.axes.yaxis.set_major_locator(MaxNLocator(integer=True))
|
| 249 |
+
|
| 250 |
+
# Set y-axis limits
|
| 251 |
+
self.repeat_vs_chromosome_canvas.axes.set_ylim(0, max(y) + 1)
|
| 252 |
+
|
| 253 |
+
# Set x-axis ticks and labels
|
| 254 |
+
self.repeat_vs_chromosome_canvas.axes.set_xticks(x)
|
| 255 |
+
self.repeat_vs_chromosome_canvas.axes.set_xticklabels(x_labels)
|
| 256 |
+
|
| 257 |
+
# If many chromosomes, show only some labels
|
| 258 |
+
if len(x_labels) > 10:
|
| 259 |
+
tick_spacing = round(len(x_labels)/10)
|
| 260 |
+
for i, t in enumerate(self.repeat_vs_chromosome_canvas.axes.get_xticklabels()):
|
| 261 |
+
if (i % tick_spacing) != 0:
|
| 262 |
+
t.set_visible(False)
|
| 263 |
+
|
| 264 |
+
# Set labels and title
|
| 265 |
+
self.repeat_vs_chromosome_canvas.axes.set_xlabel('Chromosome', fontsize=10)
|
| 266 |
+
self.repeat_vs_chromosome_canvas.axes.set_ylabel('Number of Repeats', fontsize=10)
|
| 267 |
+
self.repeat_vs_chromosome_canvas.axes.set_title('Repeats per Chromosome', fontsize=10)
|
| 268 |
+
|
| 269 |
+
# Set tick label size
|
| 270 |
+
self.repeat_vs_chromosome_canvas.axes.tick_params(axis='both', which='major', labelsize=8)
|
| 271 |
+
|
| 272 |
+
self.repeat_vs_chromosome_canvas.draw()
|
| 273 |
+
|
| 274 |
except Exception as e:
|
| 275 |
+
self.logger.error(f"Error updating repeat vs chromosome plot: {str(e)}")
|
| 276 |
|
| 277 |
+
def fill_chromosome_viewer(self, seed_data, event_data):
|
| 278 |
+
"""Fill the chromosome viewer with visualization"""
|
| 279 |
try:
|
| 280 |
+
# Clear out old widgets in layout
|
| 281 |
+
for i in reversed(range(self.chromosome_layout.count())):
|
| 282 |
+
self.chromosome_layout.itemAt(i).widget().setParent(None)
|
| 283 |
+
|
| 284 |
+
# Get sorted list of chromosomes
|
| 285 |
+
chromo_keys = sorted(list(seed_data.keys()))
|
| 286 |
+
|
| 287 |
+
# Get screen height for scaling
|
| 288 |
+
screen = self.screen()
|
| 289 |
+
height = screen.geometry().height()
|
| 290 |
+
groupbox_height = int((height * 100) / 1080)
|
| 291 |
+
|
| 292 |
+
# Create visualization for each chromosome
|
| 293 |
+
for chromo in chromo_keys:
|
| 294 |
+
group_box = QtWidgets.QGroupBox()
|
| 295 |
+
group_box.setTitle(f"Chromosome {chromo}")
|
| 296 |
+
group_box.setMinimumHeight(groupbox_height)
|
| 297 |
+
group_box.setMaximumHeight(groupbox_height)
|
| 298 |
+
layout = QtWidgets.QVBoxLayout(group_box)
|
| 299 |
+
|
| 300 |
+
# Create canvas for this chromosome
|
| 301 |
+
canvas = MplCanvas()
|
| 302 |
+
canvas.axes.eventplot(seed_data[chromo])
|
| 303 |
+
canvas.mpl_connect("motion_notify_event", self._chromosome_event_handler)
|
| 304 |
+
|
| 305 |
+
# Add border lines
|
| 306 |
+
canvas.axes.hlines(1.5, -0.01, 1.01, colors="Black", linewidth=1.5)
|
| 307 |
+
canvas.axes.hlines(0.5, -0.01, 1.01, colors="Black", linewidth=1.5)
|
| 308 |
+
canvas.axes.vlines(-0.01, 0.5, 1.5, colors="Black", linewidth=1.5)
|
| 309 |
+
canvas.axes.vlines(1.01, 0.5, 1.5, colors="Black", linewidth=1.5)
|
| 310 |
+
|
| 311 |
+
# Set axis limits
|
| 312 |
+
canvas.axes.set_ylim(0.45, 1.55)
|
| 313 |
+
canvas.axes.set_xlim(-0.05, 1.05)
|
| 314 |
+
canvas.axes.axis('off')
|
| 315 |
+
canvas.draw()
|
| 316 |
+
|
| 317 |
+
# Store canvas mapping
|
| 318 |
+
self.canvas_chromosome_map[canvas] = chromo
|
| 319 |
+
self.event_data = event_data # Store event data for hover details
|
| 320 |
+
|
| 321 |
+
layout.addWidget(canvas)
|
| 322 |
+
self.chromosome_layout.addWidget(group_box)
|
| 323 |
+
|
| 324 |
+
except Exception as e:
|
| 325 |
+
show_error(self.settings, "Error in fill_chromosome_viewer", str(e))
|
| 326 |
+
|
| 327 |
+
def _chromosome_event_handler(self, event):
|
| 328 |
+
"""Handle mouse events on chromosome visualization"""
|
| 329 |
+
try:
|
| 330 |
+
# Get current mouse location
|
| 331 |
+
x = event.xdata
|
| 332 |
+
y = event.y
|
| 333 |
+
if x is None: # Mouse outside the plot
|
| 334 |
+
return
|
| 335 |
+
|
| 336 |
+
# Get event data relative to the canvas
|
| 337 |
+
curr_chromosome = self.canvas_chromosome_map[event.canvas]
|
| 338 |
+
chromosome_seed_data = self.event_data[curr_chromosome]
|
| 339 |
+
|
| 340 |
+
# Get targets within range of mouse location
|
| 341 |
+
local_targets = []
|
| 342 |
+
for entry in chromosome_seed_data:
|
| 343 |
+
try:
|
| 344 |
+
if abs(x - entry[0]) <= 0.001:
|
| 345 |
+
local_targets.append(entry)
|
| 346 |
+
except:
|
| 347 |
+
pass
|
| 348 |
+
|
| 349 |
+
# Update viewer with target details if found
|
| 350 |
+
if local_targets:
|
| 351 |
+
self.scene2 = QtWidgets.QGraphicsScene()
|
| 352 |
+
self.graphical_view_chromosome.setScene(self.scene2)
|
| 353 |
+
|
| 354 |
+
output = ""
|
| 355 |
+
for target in local_targets:
|
| 356 |
+
output += f"Location: {target[1]} | Seq: {target[2]} | PAM: {target[3]} | SCR: {target[4]} | DIRA: {target[5]}\n"
|
| 357 |
+
|
| 358 |
+
text = self.scene2.addText(output)
|
| 359 |
+
font = QtGui.QFont()
|
| 360 |
+
font.setPointSize(self.settings.fontSize if hasattr(self.settings, 'fontSize') else 12)
|
| 361 |
+
text.setFont(font)
|
| 362 |
+
|
| 363 |
except Exception as e:
|
| 364 |
+
self.logger.error(f"Error in chromosome event handler: {str(e)}")
|
| 365 |
+
|
| 366 |
+
class MplCanvas(FigureCanvasQTAgg):
|
| 367 |
+
def __init__(self, parent=None, width=8, height=6, dpi=100):
|
| 368 |
+
fig = Figure(figsize=(width, height), dpi=dpi, tight_layout=True)
|
| 369 |
+
self.axes = fig.add_subplot(111)
|
| 370 |
+
super(MplCanvas, self).__init__(fig)
|
|
@@ -5,7 +5,8 @@ class NCBIRenameWindowView(QtWidgets.QDialog):
|
|
| 5 |
def __init__(self, settings, files):
|
| 6 |
super(NCBIRenameWindowView, self).__init__()
|
| 7 |
self.settings = settings
|
| 8 |
-
|
|
|
|
| 9 |
self.setup_ui()
|
| 10 |
|
| 11 |
def setup_ui(self):
|
|
@@ -42,6 +43,7 @@ class NCBIRenameWindowView(QtWidgets.QDialog):
|
|
| 42 |
def populate_table(self):
|
| 43 |
self.rename_table.setRowCount(len(self.files))
|
| 44 |
for row, file in enumerate(self.files):
|
|
|
|
| 45 |
item = QtWidgets.QTableWidgetItem(file)
|
| 46 |
item.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled)
|
| 47 |
self.rename_table.setItem(row, 0, item)
|
|
|
|
| 5 |
def __init__(self, settings, files):
|
| 6 |
super(NCBIRenameWindowView, self).__init__()
|
| 7 |
self.settings = settings
|
| 8 |
+
# Store only filenames, not full paths
|
| 9 |
+
self.files = [os.path.basename(file) for file in files]
|
| 10 |
self.setup_ui()
|
| 11 |
|
| 12 |
def setup_ui(self):
|
|
|
|
| 43 |
def populate_table(self):
|
| 44 |
self.rename_table.setRowCount(len(self.files))
|
| 45 |
for row, file in enumerate(self.files):
|
| 46 |
+
# Create item with just the filename
|
| 47 |
item = QtWidgets.QTableWidgetItem(file)
|
| 48 |
item.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled)
|
| 49 |
self.rename_table.setItem(row, 0, item)
|
|
@@ -4,76 +4,110 @@ import os
|
|
| 4 |
from typing import Optional
|
| 5 |
|
| 6 |
class NCBIWindowView(QtWidgets.QMainWindow):
|
|
|
|
|
|
|
| 7 |
def __init__(self, settings):
|
| 8 |
super(NCBIWindowView, self).__init__()
|
| 9 |
self.settings = settings
|
| 10 |
self.logger = settings.get_logger()
|
| 11 |
self.progress_bars = {}
|
| 12 |
self.progress_labels = {}
|
| 13 |
-
|
| 14 |
-
self.
|
| 15 |
-
|
| 16 |
-
def
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
def _init_ui_components(self) -> None:
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
def _init_grpStep1(self) -> None:
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
def _init_grpStep2(self) -> None:
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
def _init_grpStep3(self) -> None:
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
self.settings.logger.warning(f"Widget '{name}' not found in UI file.")
|
| 55 |
-
return widget
|
| 56 |
-
|
| 57 |
-
def set_styles(self):
|
| 58 |
-
groupbox_style = """
|
| 59 |
-
QGroupBox:title{subcontrol-origin: margin;
|
| 60 |
-
left: 10px;
|
| 61 |
-
padding: 0 5px 0 5px;}
|
| 62 |
-
QGroupBox#Step1{border: 2px solid rgb(111,181,110);
|
| 63 |
-
border-radius: 9px;
|
| 64 |
-
font: bold 14pt 'Arial';
|
| 65 |
-
margin-top: 10px;}"""
|
| 66 |
-
self.Step1.setStyleSheet(groupbox_style)
|
| 67 |
-
self.Step2.setStyleSheet(groupbox_style.replace("Step1","Step2"))
|
| 68 |
-
self.Step3.setStyleSheet(groupbox_style.replace("Step1","Step3"))
|
| 69 |
|
| 70 |
def populate_ncbi_table(self, model):
|
|
|
|
| 71 |
self.table_ncbi_results.setModel(model)
|
| 72 |
|
| 73 |
# Set selection behavior to select entire rows
|
| 74 |
self.table_ncbi_results.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
|
| 75 |
self.table_ncbi_results.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.MultiSelection)
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
# Set the horizontal header to resize mode
|
| 78 |
header = self.table_ncbi_results.horizontalHeader()
|
| 79 |
header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.Interactive)
|
|
@@ -104,6 +138,9 @@ class NCBIWindowView(QtWidgets.QMainWindow):
|
|
| 104 |
|
| 105 |
# Ensure the last column doesn't stretch
|
| 106 |
header.setStretchLastSection(False)
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
def get_search_parameters(self):
|
| 109 |
return {
|
|
@@ -145,3 +182,8 @@ class NCBIWindowView(QtWidgets.QMainWindow):
|
|
| 145 |
if source_model:
|
| 146 |
source_model.clear()
|
| 147 |
self.table_ncbi_results.reset()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
from typing import Optional
|
| 5 |
|
| 6 |
class NCBIWindowView(QtWidgets.QMainWindow):
|
| 7 |
+
initialization_complete = QtCore.pyqtSignal() # New signal
|
| 8 |
+
|
| 9 |
def __init__(self, settings):
|
| 10 |
super(NCBIWindowView, self).__init__()
|
| 11 |
self.settings = settings
|
| 12 |
self.logger = settings.get_logger()
|
| 13 |
self.progress_bars = {}
|
| 14 |
self.progress_labels = {}
|
| 15 |
+
self._is_initialized = False # Track initialization state
|
| 16 |
+
self._setup_basic_ui() # Only do basic initialization first
|
| 17 |
+
|
| 18 |
+
def _setup_basic_ui(self):
|
| 19 |
+
"""Initial minimal setup to show the window quickly"""
|
| 20 |
+
try:
|
| 21 |
+
uic.loadUi(os.path.join(self.settings.get_ui_dir_path(), "ncbi_window_v2.ui"), self)
|
| 22 |
+
|
| 23 |
+
QtCore.QTimer.singleShot(100, self._complete_initialization)
|
| 24 |
+
|
| 25 |
+
except Exception as e:
|
| 26 |
+
self.logger.error(f"Error in basic UI setup: {str(e)}")
|
| 27 |
+
raise
|
| 28 |
+
|
| 29 |
+
def _complete_initialization(self):
|
| 30 |
+
"""Complete the full initialization of UI components"""
|
| 31 |
+
try:
|
| 32 |
+
if self._is_initialized:
|
| 33 |
+
return
|
| 34 |
+
|
| 35 |
+
# Initialize all UI components
|
| 36 |
+
self._init_ui_components()
|
| 37 |
+
|
| 38 |
+
self._is_initialized = True
|
| 39 |
+
self.logger.debug("NCBI Window initialization completed")
|
| 40 |
+
|
| 41 |
+
# Emit signal after everything is initialized
|
| 42 |
+
self.initialization_complete.emit()
|
| 43 |
+
|
| 44 |
+
except Exception as e:
|
| 45 |
+
self.logger.error(f"Error in complete initialization: {str(e)}")
|
| 46 |
+
raise
|
| 47 |
|
| 48 |
def _init_ui_components(self) -> None:
|
| 49 |
+
"""Initialize all UI components at once instead of using timers"""
|
| 50 |
+
try:
|
| 51 |
+
self._init_grpStep1()
|
| 52 |
+
self._init_grpStep2()
|
| 53 |
+
self._init_grpStep3()
|
| 54 |
+
except Exception as e:
|
| 55 |
+
self.logger.error(f"Error in _init_ui_components: {str(e)}")
|
| 56 |
+
raise
|
| 57 |
|
| 58 |
def _init_grpStep1(self) -> None:
|
| 59 |
+
try:
|
| 60 |
+
self.line_edit_organism = self._find_widget("ledOrganism", QtWidgets.QLineEdit)
|
| 61 |
+
self.line_edit_strain = self._find_widget("ledStrain", QtWidgets.QLineEdit)
|
| 62 |
+
self.line_edit_max_results = self._find_widget("ledMaxResults", QtWidgets.QLineEdit)
|
| 63 |
+
self.check_box_complete_genomes_only = self._find_widget("chkCompleteGenomesOnly", QtWidgets.QCheckBox)
|
| 64 |
+
|
| 65 |
+
# Set default values
|
| 66 |
+
self.line_edit_max_results.setText("100")
|
| 67 |
+
|
| 68 |
+
except Exception as e:
|
| 69 |
+
self.logger.error(f"Error initializing Step 1: {str(e)}")
|
| 70 |
|
| 71 |
def _init_grpStep2(self) -> None:
|
| 72 |
+
try:
|
| 73 |
+
self.push_button_search = self._find_widget("pbtnSearch", QtWidgets.QPushButton)
|
| 74 |
+
self.check_box_select_all_rows = self._find_widget("chkSelectAllRows", QtWidgets.QCheckBox)
|
| 75 |
+
self.table_ncbi_results = self._find_widget("tblNCBIResults", QtWidgets.QTableView)
|
| 76 |
+
except Exception as e:
|
| 77 |
+
self.logger.error(f"Error initializing Step 2: {str(e)}")
|
| 78 |
|
| 79 |
def _init_grpStep3(self) -> None:
|
| 80 |
+
try:
|
| 81 |
+
self.radio_button_collections_refseq = self._find_widget("rbtnCollectionsRefSeq", QtWidgets.QRadioButton)
|
| 82 |
+
self.radio_button_collections_genbank = self._find_widget("rbtnCollectionsGenBank", QtWidgets.QRadioButton)
|
| 83 |
+
self.check_box_file_types_fna = self._find_widget("chkFileTypesFNA", QtWidgets.QCheckBox)
|
| 84 |
+
self.check_box_file_types_gbff = self._find_widget("chkFileTypesGBFF", QtWidgets.QCheckBox)
|
| 85 |
+
self.push_button_download_files = self._find_widget("pbtnDownloadFiles", QtWidgets.QPushButton)
|
| 86 |
+
self.progress_bar_download_files = self._find_widget("pbDownloadFiles", QtWidgets.QProgressBar)
|
| 87 |
+
self.label_download_files_status = self._find_widget("lblDownloadFilesStatus", QtWidgets.QLabel)
|
| 88 |
+
|
| 89 |
+
# Set initial states
|
| 90 |
+
self.progress_bar_download_files.setValue(0)
|
| 91 |
+
self.radio_button_collections_refseq.setChecked(True)
|
| 92 |
+
self.check_box_file_types_fna.setChecked(True)
|
| 93 |
+
|
| 94 |
+
except Exception as e:
|
| 95 |
+
self.logger.error(f"Error initializing Step 3: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
def populate_ncbi_table(self, model):
|
| 98 |
+
"""Populate the table with data and set up proper row selection"""
|
| 99 |
self.table_ncbi_results.setModel(model)
|
| 100 |
|
| 101 |
# Set selection behavior to select entire rows
|
| 102 |
self.table_ncbi_results.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
|
| 103 |
self.table_ncbi_results.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.MultiSelection)
|
| 104 |
|
| 105 |
+
# Disable cell editing
|
| 106 |
+
self.table_ncbi_results.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
|
| 107 |
+
|
| 108 |
+
# Enable sorting
|
| 109 |
+
self.table_ncbi_results.setSortingEnabled(True)
|
| 110 |
+
|
| 111 |
# Set the horizontal header to resize mode
|
| 112 |
header = self.table_ncbi_results.horizontalHeader()
|
| 113 |
header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.Interactive)
|
|
|
|
| 138 |
|
| 139 |
# Ensure the last column doesn't stretch
|
| 140 |
header.setStretchLastSection(False)
|
| 141 |
+
|
| 142 |
+
# Set focus policy to enable keyboard selection
|
| 143 |
+
self.table_ncbi_results.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
|
| 144 |
|
| 145 |
def get_search_parameters(self):
|
| 146 |
return {
|
|
|
|
| 182 |
if source_model:
|
| 183 |
source_model.clear()
|
| 184 |
self.table_ncbi_results.reset()
|
| 185 |
+
def _find_widget(self, name: str, widget_type: type) -> Optional[QtWidgets.QWidget]:
|
| 186 |
+
widget = self.findChild(widget_type, name)
|
| 187 |
+
if widget is None:
|
| 188 |
+
self.settings.logger.warning(f"Widget '{name}' not found in UI file.")
|
| 189 |
+
return widget
|
|
@@ -1,228 +0,0 @@
|
|
| 1 |
-
import sys, os
|
| 2 |
-
from PyQt5 import QtWidgets, uic, QtGui, QtCore, Qt
|
| 3 |
-
import models.GlobalSettings as GlobalSettings
|
| 4 |
-
from PyQt5.QtGui import QIntValidator
|
| 5 |
-
import traceback
|
| 6 |
-
import math
|
| 7 |
-
from utils.ui import show_message, show_error, scale_ui, center_ui
|
| 8 |
-
|
| 9 |
-
logger = GlobalSettings.logger
|
| 10 |
-
|
| 11 |
-
class NewEndonuclease(QtWidgets.QMainWindow):
|
| 12 |
-
def __init__(self):
|
| 13 |
-
print("Initializing NewEndonuclease class")
|
| 14 |
-
try:
|
| 15 |
-
super(NewEndonuclease, self).__init__()
|
| 16 |
-
uic.loadUi(GlobalSettings.appdir + 'ui/newendonuclease.ui', self)
|
| 17 |
-
self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
|
| 18 |
-
self.setWindowTitle('New Endonuclease')
|
| 19 |
-
self.error = False
|
| 20 |
-
pamFlag = False
|
| 21 |
-
|
| 22 |
-
self.onList = []
|
| 23 |
-
self.offList = []
|
| 24 |
-
|
| 25 |
-
self.onList, self.offList = self.get_on_off_data() ### Call function to fill on- and off- data name lists
|
| 26 |
-
|
| 27 |
-
for name in self.onList: ### Add on-target names to drop-down
|
| 28 |
-
self.comboBox.addItem(str(name))
|
| 29 |
-
|
| 30 |
-
for name in self.offList: ### Add off-target names to drop-down
|
| 31 |
-
self.comboBox_2.addItem(str(name))
|
| 32 |
-
|
| 33 |
-
self.submit_button.clicked.connect(self.submit)
|
| 34 |
-
self.cancel_button.clicked.connect(self.cancel)
|
| 35 |
-
|
| 36 |
-
### Set up validators for input fields:
|
| 37 |
-
reg_ex1 = QtCore.QRegExp("[^/\\\\_]+") # No slashes or underscores
|
| 38 |
-
reg_ex2 = QtCore.QRegExp("[^/\\\\_\\s]+") # No slashes, underscores, or spaces
|
| 39 |
-
reg_ex3 = QtCore.QRegExp("[acdefghiklmnpqrstvwyACDEFGHIKLMNPQRSTVWY\S]+") # Only approved PAM characters and no spaces
|
| 40 |
-
input_validator1 = QtGui.QRegExpValidator(reg_ex1, self)
|
| 41 |
-
input_validator2 = QtGui.QRegExpValidator(reg_ex2, self)
|
| 42 |
-
input_validator3 = QtGui.QRegExpValidator(reg_ex3, self)
|
| 43 |
-
self.organism_name.setValidator(input_validator1)
|
| 44 |
-
self.abbreviation.setValidator(input_validator2)
|
| 45 |
-
self.pam_sequence.setValidator(input_validator3)
|
| 46 |
-
|
| 47 |
-
self.seed_length.setValidator(QIntValidator(0,30,self.seed_length))
|
| 48 |
-
self.five_length.setValidator(QIntValidator(0,20,self.five_length))
|
| 49 |
-
self.three_length.setValidator(QIntValidator(0,20,self.three_length))
|
| 50 |
-
|
| 51 |
-
groupbox_style = """
|
| 52 |
-
QGroupBox:title{subcontrol-origin: margin;
|
| 53 |
-
left: 10px;
|
| 54 |
-
padding: 0 5px 0 5px;}
|
| 55 |
-
QGroupBox#groupBox{border: 2px solid rgb(111,181,110);
|
| 56 |
-
border-radius: 9px;
|
| 57 |
-
font: bold 14pt 'Arial';
|
| 58 |
-
margin-top: 10px;}"""
|
| 59 |
-
|
| 60 |
-
self.groupBox.setStyleSheet(groupbox_style)
|
| 61 |
-
self.groupBox_2.setStyleSheet(groupbox_style.replace("groupBox","groupBox_2"))
|
| 62 |
-
self.groupBox_3.setStyleSheet(groupbox_style.replace("groupBox","groupBox_3"))
|
| 63 |
-
|
| 64 |
-
scale_ui(self, custom_scale_width=480, custom_scale_height=615)
|
| 65 |
-
except Exception as e:
|
| 66 |
-
show_error("Error initializing NewEndonuclease class.", e)
|
| 67 |
-
|
| 68 |
-
#helper function for writing new endo information to CASPERinfo - used by submit()
|
| 69 |
-
def writeNewEndonuclease(self, newEndonucleaseStr):
|
| 70 |
-
try:
|
| 71 |
-
with open(GlobalSettings.appdir + 'CASPERinfo', 'r') as f, open(GlobalSettings.appdir + "new_file", 'w+') as f1:
|
| 72 |
-
for line in f:
|
| 73 |
-
f1.write(line)
|
| 74 |
-
if 'ENDONUCLEASES' in line:
|
| 75 |
-
f1.write(newEndonucleaseStr + '\n') # Move f1.write(line) above, to write above instead
|
| 76 |
-
os.remove(GlobalSettings.appdir + "CASPERinfo")
|
| 77 |
-
os.rename(GlobalSettings.appdir + "new_file",
|
| 78 |
-
GlobalSettings.appdir + "CASPERinfo") # Rename the new file
|
| 79 |
-
except Exception as e:
|
| 80 |
-
show_error("Error in writeNewEndonuclease() in New Endonuclease.", e)
|
| 81 |
-
|
| 82 |
-
#submit new endo to CASPERinfo file
|
| 83 |
-
def submit(self):
|
| 84 |
-
try:
|
| 85 |
-
# This is executed when the button is pressed
|
| 86 |
-
name = str(self.organism_name.text())
|
| 87 |
-
abbr = str(self.abbreviation.text())
|
| 88 |
-
crisprtype = str(self.crispr_type.text())
|
| 89 |
-
seed_len = str(self.seed_length.text())
|
| 90 |
-
five_len = str(self.five_length.text())
|
| 91 |
-
three_len = str(self.three_length.text())
|
| 92 |
-
pam = str(self.pam_sequence.text()).upper()
|
| 93 |
-
### Check for multiple PAMs and format if present
|
| 94 |
-
if len(pam.split(','))>0:
|
| 95 |
-
pam = [x.strip() for x in pam.split(',')]
|
| 96 |
-
pam = ",".join(pam)
|
| 97 |
-
### Check for PAM directionality
|
| 98 |
-
if self.five_pam.isChecked():
|
| 99 |
-
pam_dir = str(5)
|
| 100 |
-
else:
|
| 101 |
-
pam_dir = str(3)
|
| 102 |
-
on_scoring = str(self.comboBox.currentText())
|
| 103 |
-
off_scoring = str(self.comboBox_2.currentText())
|
| 104 |
-
length = len(seed_len) + len(five_len) + len(three_len)
|
| 105 |
-
argument_list = [abbr, pam, five_len, seed_len, three_len, pam_dir, name, crisprtype, on_scoring, off_scoring]
|
| 106 |
-
validPAM = ('A', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'Y')
|
| 107 |
-
self.error = False;
|
| 108 |
-
|
| 109 |
-
### Error checking for PAM alphabet
|
| 110 |
-
for letter in pam:
|
| 111 |
-
if (letter not in validPAM):
|
| 112 |
-
show_message(
|
| 113 |
-
fontSize=12,
|
| 114 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 115 |
-
title="Invalid PAM",
|
| 116 |
-
message="Invalid characters in PAM Sequence."
|
| 117 |
-
)
|
| 118 |
-
return True
|
| 119 |
-
### Error checking for filling out all fields
|
| 120 |
-
for arg in argument_list:
|
| 121 |
-
if ';' in arg:
|
| 122 |
-
show_message(
|
| 123 |
-
fontSize=12,
|
| 124 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 125 |
-
title="Invalid Semicolon",
|
| 126 |
-
message="Invalid character used: ';'."
|
| 127 |
-
)
|
| 128 |
-
return True
|
| 129 |
-
elif arg == "":
|
| 130 |
-
show_message(
|
| 131 |
-
fontSize=12,
|
| 132 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 133 |
-
title="Empty Field",
|
| 134 |
-
message="Please fill in all fields."
|
| 135 |
-
)
|
| 136 |
-
return True
|
| 137 |
-
else:
|
| 138 |
-
pass
|
| 139 |
-
|
| 140 |
-
### Check for duplicate endo abbreviations
|
| 141 |
-
for key in GlobalSettings.mainWindow.organisms_to_endos:
|
| 142 |
-
endo = GlobalSettings.mainWindow.organisms_to_endos[key]
|
| 143 |
-
if abbr in endo:
|
| 144 |
-
show_message(
|
| 145 |
-
fontSize=12,
|
| 146 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 147 |
-
title="Duplicate endo name.",
|
| 148 |
-
message="The given abbreviation already exists. Please choose a unique identifier."
|
| 149 |
-
)
|
| 150 |
-
return True
|
| 151 |
-
else:
|
| 152 |
-
pass
|
| 153 |
-
|
| 154 |
-
myString = ""
|
| 155 |
-
for i, arg in enumerate(argument_list):
|
| 156 |
-
if i == len(argument_list)-1: ### Last argument in list
|
| 157 |
-
myString += str(arg)
|
| 158 |
-
else:
|
| 159 |
-
myString += str(arg) + ";"
|
| 160 |
-
|
| 161 |
-
self.writeNewEndonuclease(myString)
|
| 162 |
-
|
| 163 |
-
### Refresh endonuclease dropdown in New Genome
|
| 164 |
-
GlobalSettings.mainWindow.newGenome.fillEndo()
|
| 165 |
-
|
| 166 |
-
self.clear_all()
|
| 167 |
-
self.close()
|
| 168 |
-
except Exception as e:
|
| 169 |
-
show_error("Error in submit() in New Endonuclease.", e)
|
| 170 |
-
|
| 171 |
-
#cancel and close window
|
| 172 |
-
def cancel(self):
|
| 173 |
-
try:
|
| 174 |
-
self.clear_all()
|
| 175 |
-
self.close()
|
| 176 |
-
except Exception as e:
|
| 177 |
-
show_error("Error in cancel() in New Endonuclease.", e)
|
| 178 |
-
|
| 179 |
-
# This function clears all of the line edits
|
| 180 |
-
def clear_all(self):
|
| 181 |
-
try:
|
| 182 |
-
self.organism_name.clear()
|
| 183 |
-
self.abbreviation.clear()
|
| 184 |
-
self.crispr_type.clear()
|
| 185 |
-
self.seed_length.clear()
|
| 186 |
-
self.five_length.clear()
|
| 187 |
-
self.three_length.clear()
|
| 188 |
-
self.pam_sequence.clear()
|
| 189 |
-
except Exception as e:
|
| 190 |
-
show_error("Error in clear_all() in New Endonuclease.", e)
|
| 191 |
-
|
| 192 |
-
# This function parses CASPERinfo to return the names (in lists) of all on-target and off-target scoring data
|
| 193 |
-
def get_on_off_data(self):
|
| 194 |
-
try:
|
| 195 |
-
filename = GlobalSettings.appdir + "CASPERinfo"
|
| 196 |
-
retList_on = []
|
| 197 |
-
retList_off = []
|
| 198 |
-
with open(filename, 'r') as f:
|
| 199 |
-
lines = f.readlines()
|
| 200 |
-
for i, line in enumerate(lines):
|
| 201 |
-
line = str(line)
|
| 202 |
-
if "ON-TARGET DATA" in line:
|
| 203 |
-
index = i
|
| 204 |
-
while "-----" not in line:
|
| 205 |
-
if "DATA:" in line:
|
| 206 |
-
retList_on.append(line.split("DATA:")[-1].strip()) ### Append name of scoring data to on-target name list
|
| 207 |
-
line = lines[index+1]
|
| 208 |
-
index += 1
|
| 209 |
-
else:
|
| 210 |
-
line = lines[index+1]
|
| 211 |
-
index += 1
|
| 212 |
-
continue
|
| 213 |
-
elif "OFF-TARGET MATRICES" in line:
|
| 214 |
-
index = i
|
| 215 |
-
while "-----" not in line:
|
| 216 |
-
if "MATRIX:" in line:
|
| 217 |
-
retList_off.append(line.split("MATRIX:")[-1].strip()) ### Append name of scoring data to off-target name list
|
| 218 |
-
line = lines[index+1]
|
| 219 |
-
index += 1
|
| 220 |
-
else:
|
| 221 |
-
line = lines[index+1]
|
| 222 |
-
index += 1
|
| 223 |
-
continue
|
| 224 |
-
else:
|
| 225 |
-
continue
|
| 226 |
-
return retList_on, retList_off
|
| 227 |
-
except Exception as e:
|
| 228 |
-
show_error("Error in get_on_off_data() in New Endonuclease.", e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,705 +0,0 @@
|
|
| 1 |
-
from ast import Global
|
| 2 |
-
import os
|
| 3 |
-
from PyQt5 import QtWidgets, uic, QtGui, QtCore, Qt
|
| 4 |
-
import models.GlobalSettings as GlobalSettings
|
| 5 |
-
from functools import partial
|
| 6 |
-
from utils.Algorithms import SeqTranslate
|
| 7 |
-
import webbrowser
|
| 8 |
-
import platform
|
| 9 |
-
import traceback
|
| 10 |
-
import math
|
| 11 |
-
from utils.ui import show_message, show_error, scale_ui, center_ui
|
| 12 |
-
from utils.web import ncbi_page, repo_page
|
| 13 |
-
|
| 14 |
-
logger = GlobalSettings.logger
|
| 15 |
-
|
| 16 |
-
def iter_except(function, exception):
|
| 17 |
-
"""Works like builtin 2-argument `iter()`, but stops on `exception`."""
|
| 18 |
-
try:
|
| 19 |
-
while True:
|
| 20 |
-
yield function()
|
| 21 |
-
except exception:
|
| 22 |
-
return
|
| 23 |
-
|
| 24 |
-
#UI prompt for when the user has finished running jobs in new genome to allow them to choose where the want to proceed
|
| 25 |
-
class goToPrompt(QtWidgets.QMainWindow):
|
| 26 |
-
def __init__(self):
|
| 27 |
-
try:
|
| 28 |
-
super(goToPrompt, self).__init__()
|
| 29 |
-
uic.loadUi(GlobalSettings.appdir + 'ui/newgenomenavigationpage.ui', self)
|
| 30 |
-
|
| 31 |
-
groupbox_style = """
|
| 32 |
-
QGroupBox:title{subcontrol-origin: margin;
|
| 33 |
-
left: 10px;
|
| 34 |
-
padding: 0 5px 0 5px;}
|
| 35 |
-
QGroupBox#groupBox{border: 2px solid rgb(111,181,110);
|
| 36 |
-
border-radius: 9px;
|
| 37 |
-
font: bold 14pt 'Arial';
|
| 38 |
-
margin-top: 10px;}"""
|
| 39 |
-
self.groupBox.setStyleSheet(groupbox_style)
|
| 40 |
-
scale_ui(self, custom_scale_width=575, custom_scale_height=175)
|
| 41 |
-
self.setWindowTitle("New Genome")
|
| 42 |
-
self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
|
| 43 |
-
self.hide()
|
| 44 |
-
|
| 45 |
-
except Exception as e:
|
| 46 |
-
show_error("Unable to initialize goToPrompt class in New Genome.", e)
|
| 47 |
-
|
| 48 |
-
#New genome class to allow users to generate new CSPR files
|
| 49 |
-
class NewGenome(QtWidgets.QMainWindow):
|
| 50 |
-
def __init__(self, info_path):
|
| 51 |
-
try:
|
| 52 |
-
super(NewGenome, self).__init__()
|
| 53 |
-
uic.loadUi(GlobalSettings.appdir + 'ui/NewGenome.ui', self)
|
| 54 |
-
self.setWindowTitle('New Genome')
|
| 55 |
-
self.setWindowTitle('New Genome')
|
| 56 |
-
self.info_path = info_path
|
| 57 |
-
|
| 58 |
-
#---Style Modifications---#
|
| 59 |
-
|
| 60 |
-
groupbox_style = """
|
| 61 |
-
QGroupBox:title{subcontrol-origin: margin;
|
| 62 |
-
left: 10px;
|
| 63 |
-
padding: 0 5px 0 5px;}
|
| 64 |
-
QGroupBox#Step1{border: 2px solid rgb(111,181,110);
|
| 65 |
-
border-radius: 9px;
|
| 66 |
-
font: bold 14pt 'Arial';
|
| 67 |
-
margin-top: 10px;}"""
|
| 68 |
-
|
| 69 |
-
self.Step1.setStyleSheet(groupbox_style)
|
| 70 |
-
self.Step2.setStyleSheet(groupbox_style.replace("Step1","Step2"))
|
| 71 |
-
self.Step3.setStyleSheet(groupbox_style.replace("Step1","Step3"))
|
| 72 |
-
|
| 73 |
-
#---Button Modifications---#
|
| 74 |
-
|
| 75 |
-
self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
|
| 76 |
-
self.resetButton.clicked.connect(self.reset)
|
| 77 |
-
self.submitButton.clicked.connect(self.submit)
|
| 78 |
-
self.browseForFile.clicked.connect(self.selectFasta)
|
| 79 |
-
self.remove_job.clicked.connect(self.remove_from_queue)
|
| 80 |
-
self.output_browser.setText("Waiting for program initiation...")
|
| 81 |
-
self.contButton.clicked.connect(self.continue_to_main)
|
| 82 |
-
|
| 83 |
-
self.comboBoxEndo.currentIndexChanged.connect(self.endo_settings)
|
| 84 |
-
|
| 85 |
-
self.runButton.clicked.connect(self.run_jobs_wrapper)
|
| 86 |
-
self.clearButton.clicked.connect(self.clear_all)
|
| 87 |
-
|
| 88 |
-
self.JobsQueue = [] # holds Job classes.
|
| 89 |
-
self.check_strings = []
|
| 90 |
-
self.Endos = dict()
|
| 91 |
-
self.file = ""
|
| 92 |
-
|
| 93 |
-
self.process = QtCore.QProcess()
|
| 94 |
-
self.process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
|
| 95 |
-
self.process.finished.connect(self.upon_process_finishing)
|
| 96 |
-
self.seqTrans = SeqTranslate()
|
| 97 |
-
self.exit = False
|
| 98 |
-
|
| 99 |
-
self.first = False
|
| 100 |
-
#show functionalities on window
|
| 101 |
-
self.fillEndo()
|
| 102 |
-
|
| 103 |
-
self.num_chromo_next = False
|
| 104 |
-
|
| 105 |
-
#Jobs Table
|
| 106 |
-
self.job_Table.setShowGrid(False)
|
| 107 |
-
self.job_Table.horizontalHeader().setSectionsClickable(True)
|
| 108 |
-
self.job_Table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
| 109 |
-
self.job_Table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
| 110 |
-
self.job_Table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
| 111 |
-
self.job_Table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
|
| 112 |
-
self.fin_index=0
|
| 113 |
-
|
| 114 |
-
self.mwfg = self.frameGeometry() ##Center window
|
| 115 |
-
self.cp = QtWidgets.QDesktopWidget().availableGeometry().center() ##Center window
|
| 116 |
-
self.total_chrom_count = 0
|
| 117 |
-
self.perc_increase = 0
|
| 118 |
-
self.progress = 0
|
| 119 |
-
|
| 120 |
-
#toolbar button actions
|
| 121 |
-
self.visit_repo.triggered.connect(repo_page)
|
| 122 |
-
self.go_ncbi.triggered.connect(ncbi_page)
|
| 123 |
-
|
| 124 |
-
self.comboBoxEndo.currentIndexChanged.connect(self.changeEndos)
|
| 125 |
-
|
| 126 |
-
### NCBI tool
|
| 127 |
-
self.NCBI_File_Search.clicked.connect(self.open_ncbi_tool)
|
| 128 |
-
|
| 129 |
-
self.seed_length.setEnabled(False)
|
| 130 |
-
self.five_length.setEnabled(False)
|
| 131 |
-
self.three_length.setEnabled(False)
|
| 132 |
-
self.repeats_box.setEnabled(False)
|
| 133 |
-
|
| 134 |
-
### User prompt class
|
| 135 |
-
self.goToPrompt = goToPrompt()
|
| 136 |
-
self.goToPrompt.goToMain.clicked.connect(self.continue_to_main)
|
| 137 |
-
self.goToPrompt.goToMT.clicked.connect(self.continue_to_MT)
|
| 138 |
-
self.goToPrompt.goToPop.clicked.connect(self.continue_to_pop)
|
| 139 |
-
|
| 140 |
-
self.orgName.setFocus()
|
| 141 |
-
|
| 142 |
-
### Connect New endonuclease to New Genome
|
| 143 |
-
self.actionUpload_New_Endonuclease.triggered.connect(self.launch_newEndonuclease)
|
| 144 |
-
|
| 145 |
-
### Set up validators for input fields:
|
| 146 |
-
reg_ex1 = QtCore.QRegExp("[^/\\\\_]+") # No slashes or underscores
|
| 147 |
-
reg_ex2 = QtCore.QRegExp("\\S+")
|
| 148 |
-
input_validator1 = QtGui.QRegExpValidator(reg_ex1, self)
|
| 149 |
-
input_validator2 = QtGui.QRegExpValidator(reg_ex2, self)
|
| 150 |
-
self.orgName.setValidator(input_validator1)
|
| 151 |
-
self.strainName.setValidator(input_validator1)
|
| 152 |
-
self.orgCode.setValidator(input_validator2)
|
| 153 |
-
|
| 154 |
-
scale_ui(self, custom_scale_width=850, custom_scale_height=750)
|
| 155 |
-
self.first_show = True
|
| 156 |
-
except Exception as e:
|
| 157 |
-
show_error("Error initializing New Genome class.", e)
|
| 158 |
-
|
| 159 |
-
def launch_newEndonuclease(self):
|
| 160 |
-
try:
|
| 161 |
-
GlobalSettings.mainWindow.getData()
|
| 162 |
-
GlobalSettings.mainWindow.newEndonuclease.centerUI()
|
| 163 |
-
GlobalSettings.mainWindow.newEndonuclease.show()
|
| 164 |
-
GlobalSettings.mainWindow.newEndonuclease.activateWindow()
|
| 165 |
-
except Exception as e:
|
| 166 |
-
show_error("Error in launch_newEndonuclease() in New Genome.", e)
|
| 167 |
-
|
| 168 |
-
#open the ncbi search tool window
|
| 169 |
-
def open_ncbi_tool(self):
|
| 170 |
-
try:
|
| 171 |
-
#center ncbi on current screen
|
| 172 |
-
if GlobalSettings.mainWindow.ncbi.first_show == True:
|
| 173 |
-
GlobalSettings.mainWindow.ncbi.first_show = False
|
| 174 |
-
GlobalSettings.mainWindow.ncbi.centerUI()
|
| 175 |
-
if self.orgName.text() != "":
|
| 176 |
-
GlobalSettings.mainWindow.ncbi.organism_line_edit.setText(self.orgName.text())
|
| 177 |
-
if self.strainName.text() != "":
|
| 178 |
-
GlobalSettings.mainWindow.ncbi.infra_name_line_edit.setText(self.strainName.text())
|
| 179 |
-
GlobalSettings.mainWindow.ncbi.show()
|
| 180 |
-
GlobalSettings.mainWindow.ncbi.activateWindow()
|
| 181 |
-
except Exception as e:
|
| 182 |
-
show_error("Error in open_ncbi_tool() in New Genome.", e)
|
| 183 |
-
|
| 184 |
-
def remove_from_queue(self):
|
| 185 |
-
try:
|
| 186 |
-
while(True):
|
| 187 |
-
indexes = self.job_Table.selectionModel().selectedRows()
|
| 188 |
-
if len(indexes) == 0:
|
| 189 |
-
break
|
| 190 |
-
self.job_Table.removeRow(indexes[0].row())
|
| 191 |
-
except Exception as e:
|
| 192 |
-
show_error("Error in remove_from_queue() in New Genome.", e)
|
| 193 |
-
|
| 194 |
-
#prompt user with file browser to select fasta/fna files
|
| 195 |
-
def selectFasta(self):
|
| 196 |
-
try:
|
| 197 |
-
filed = QtWidgets.QFileDialog()
|
| 198 |
-
myFile = QtWidgets.QFileDialog.getOpenFileName(filed, "Choose a File")
|
| 199 |
-
if (myFile[0] != ""):
|
| 200 |
-
if not myFile[0].endswith(".fa") and not myFile[0].endswith(".fna") and not myFile[0].endswith(".fasta"):
|
| 201 |
-
show_message(
|
| 202 |
-
fontSize=12,
|
| 203 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 204 |
-
title="File Selection Error",
|
| 205 |
-
message="You have selected an incorrect type of file. Please choose a FASTA/FNA file."
|
| 206 |
-
)
|
| 207 |
-
return
|
| 208 |
-
else:
|
| 209 |
-
self.file = myFile[0]
|
| 210 |
-
self.selectedFile.setText(str(myFile[0]))
|
| 211 |
-
except Exception as e:
|
| 212 |
-
show_error("Error in selectFasta() in New Genome.", e)
|
| 213 |
-
|
| 214 |
-
#submit jobs to queue
|
| 215 |
-
def submit(self):
|
| 216 |
-
try:
|
| 217 |
-
warning = ""
|
| 218 |
-
if len(self.orgName.text()) == 0:
|
| 219 |
-
warning = warning + "You need to include the organism's name."
|
| 220 |
-
if len(self.file) == 0:
|
| 221 |
-
warning = warning + "You need to select a file."
|
| 222 |
-
if len(warning) != 0:
|
| 223 |
-
show_message(
|
| 224 |
-
fontSize=12,
|
| 225 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 226 |
-
title="Required Information",
|
| 227 |
-
message=warning
|
| 228 |
-
)
|
| 229 |
-
return
|
| 230 |
-
if len(self.strainName.text()) == 0:
|
| 231 |
-
warning = warning + "\nIt is recommended to include the organism's subspecies/strain."
|
| 232 |
-
if len(self.orgCode.text()) == 0:
|
| 233 |
-
warning = warning + "\nYou must include an organism code."
|
| 234 |
-
if len(warning) != 0:
|
| 235 |
-
msgBox = QtWidgets.QMessageBox()
|
| 236 |
-
msgBox.setStyleSheet("font: " + str(self.fontSize) + "pt 'Arial'")
|
| 237 |
-
msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
|
| 238 |
-
msgBox.setWindowTitle("Missing Information")
|
| 239 |
-
msgBox.setText(warning + "\n\nDo you wish to continue without including this information?")
|
| 240 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
|
| 241 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
|
| 242 |
-
msgBox.exec()
|
| 243 |
-
|
| 244 |
-
if msgBox.result() == QtWidgets.QMessageBox.No:
|
| 245 |
-
return
|
| 246 |
-
|
| 247 |
-
#endo, pam, repeats, directionality, five length, seed length, three length, orgcode, output path, CASPERinfo path, fna path, orgName, notes, on target matrix
|
| 248 |
-
args = self.Endos[self.comboBoxEndo.currentText()][0]
|
| 249 |
-
args += " " + self.Endos[self.comboBoxEndo.currentText()][1]
|
| 250 |
-
if self.mt.isChecked():
|
| 251 |
-
args += " " + "TRUE"
|
| 252 |
-
else:
|
| 253 |
-
args += " " + "FALSE"
|
| 254 |
-
|
| 255 |
-
if self.Endos[self.comboBoxEndo.currentText()][5] == "3":
|
| 256 |
-
args += " " + "FALSE"
|
| 257 |
-
else:
|
| 258 |
-
args += " " + "TRUE"
|
| 259 |
-
|
| 260 |
-
if self.repeats_box.isChecked():
|
| 261 |
-
args += " " + "TRUE"
|
| 262 |
-
else:
|
| 263 |
-
args += " " + "FALSE"
|
| 264 |
-
|
| 265 |
-
args += " " + self.Endos[self.comboBoxEndo.currentText()][2]
|
| 266 |
-
args += " " + self.Endos[self.comboBoxEndo.currentText()][3]
|
| 267 |
-
args += " " + self.Endos[self.comboBoxEndo.currentText()][4]
|
| 268 |
-
args += " " + self.orgCode.text()
|
| 269 |
-
if platform.system() == 'Windows':
|
| 270 |
-
args += " " + '"' + GlobalSettings.CSPR_DB.replace("/","\\") + '\\"'
|
| 271 |
-
args += " " + '"' + GlobalSettings.appdir.replace("/","\\") + "CASPERinfo" + '"'
|
| 272 |
-
args += " " + '"' + self.file.replace("/","\\") + '"'
|
| 273 |
-
else:
|
| 274 |
-
args += " " + '"' + GlobalSettings.CSPR_DB.replace("\\","/") + '/"'
|
| 275 |
-
args += " " + '"' + GlobalSettings.appdir.replace("\\","/") + "CASPERinfo" + '"'
|
| 276 |
-
args += " " + '"' + self.file.replace("\\","/") + '"'
|
| 277 |
-
|
| 278 |
-
args += " " + '"' + self.orgName.text() + " " + self.strainName.text() + '"'
|
| 279 |
-
args += " " + '"' + "notes" + '"'
|
| 280 |
-
args += " " + '"DATA:' + self.Endos[self.comboBoxEndo.currentText()][6] + '"'
|
| 281 |
-
|
| 282 |
-
tmp = self.orgName.text()+ " " + self.strainName.text() + " " + self.Endos[self.comboBoxEndo.currentText()][0] + " " + self.orgCode.text()
|
| 283 |
-
if tmp in self.check_strings:
|
| 284 |
-
show_message(
|
| 285 |
-
fontSize=12,
|
| 286 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 287 |
-
title="Duplicate Entry",
|
| 288 |
-
message="You have submitted a duplicate entry. Consider changing the organism code or strain name to differentiate closely related strains."
|
| 289 |
-
)
|
| 290 |
-
return
|
| 291 |
-
name = self.orgCode.text() + "_" + str(self.Endos[self.comboBoxEndo.currentText()][0])
|
| 292 |
-
rowPosition = self.job_Table.rowCount()
|
| 293 |
-
self.job_Table.insertRow(rowPosition)
|
| 294 |
-
item = QtWidgets.QTableWidgetItem(name)
|
| 295 |
-
item.setTextAlignment(QtCore.Qt.AlignHCenter)
|
| 296 |
-
self.job_Table.setItem(rowPosition, 0, item)
|
| 297 |
-
self.check_strings.append(tmp)
|
| 298 |
-
self.JobsQueue.append(args)
|
| 299 |
-
except Exception as e:
|
| 300 |
-
show_error("Error in submit() in New Genome.", e)
|
| 301 |
-
|
| 302 |
-
#fill the endo dropdown
|
| 303 |
-
def fillEndo(self):
|
| 304 |
-
try:
|
| 305 |
-
#disconnect signal
|
| 306 |
-
try:
|
| 307 |
-
self.comboBoxEndo.currentIndexChanged.disconnect()
|
| 308 |
-
except:
|
| 309 |
-
pass
|
| 310 |
-
|
| 311 |
-
#clear out the endo box
|
| 312 |
-
self.comboBoxEndo.clear()
|
| 313 |
-
|
| 314 |
-
f = open(GlobalSettings.appdir + "CASPERinfo")
|
| 315 |
-
while True:
|
| 316 |
-
line = f.readline()
|
| 317 |
-
if line.startswith('ENDONUCLEASES'):
|
| 318 |
-
while True:
|
| 319 |
-
line = f.readline()
|
| 320 |
-
if (line[0] == "-"):
|
| 321 |
-
break
|
| 322 |
-
line_tokened = line.split(";")
|
| 323 |
-
if len(line_tokened) == 10:
|
| 324 |
-
endo = line_tokened[0]
|
| 325 |
-
# Checking to see if there is more than one pam sequence in the list
|
| 326 |
-
if line_tokened[1].find(",") != -1:
|
| 327 |
-
p_pam = line_tokened[1].split(",")[0]
|
| 328 |
-
else:
|
| 329 |
-
p_pam = line_tokened[1]
|
| 330 |
-
five_length = line_tokened[2]
|
| 331 |
-
seed_length = line_tokened[3]
|
| 332 |
-
three_length = line_tokened[4]
|
| 333 |
-
dir = line_tokened[5]
|
| 334 |
-
on_target_data = line_tokened[8]
|
| 335 |
-
self.Endos[endo + " - PAM: " + p_pam] = (endo, p_pam, five_length, seed_length, three_length, dir, on_target_data)
|
| 336 |
-
break
|
| 337 |
-
f.close()
|
| 338 |
-
self.comboBoxEndo.addItems(self.Endos.keys())
|
| 339 |
-
key = list(self.Endos.keys())[0]
|
| 340 |
-
self.seed_length.setText(self.Endos[key][3])
|
| 341 |
-
self.five_length.setText(self.Endos[key][2])
|
| 342 |
-
self.three_length.setText(self.Endos[key][4])
|
| 343 |
-
|
| 344 |
-
#reconnect signal
|
| 345 |
-
self.comboBoxEndo.currentIndexChanged.connect(self.changeEndos)
|
| 346 |
-
except Exception as e:
|
| 347 |
-
show_error("Error in fillEndo() in New Genome.", e)
|
| 348 |
-
|
| 349 |
-
#event handler for endo changing - update endo length data
|
| 350 |
-
def changeEndos(self):
|
| 351 |
-
try:
|
| 352 |
-
key = str(self.comboBoxEndo.currentText())
|
| 353 |
-
self.seed_length.setText(self.Endos[key][3])
|
| 354 |
-
self.five_length.setText(self.Endos[key][2])
|
| 355 |
-
self.three_length.setText(self.Endos[key][4])
|
| 356 |
-
except Exception as e:
|
| 357 |
-
show_error("Error in changeEndos() in New Genome.", e)
|
| 358 |
-
|
| 359 |
-
#check if endo is 3' or 5'
|
| 360 |
-
def endo_settings(self):
|
| 361 |
-
try:
|
| 362 |
-
# check the if it's 3' or 5', and check the box accordingly
|
| 363 |
-
if int(self.seqTrans.endo_info[self.Endos[self.comboBoxEndo.currentText()][0]][3]) == 3:
|
| 364 |
-
self.pamBox.setChecked(0)
|
| 365 |
-
elif int(self.seqTrans.endo_info[self.Endos[self.comboBoxEndo.currentText()][0]][3]) == 5:
|
| 366 |
-
self.pamBox.setChecked(1)
|
| 367 |
-
except Exception as e:
|
| 368 |
-
show_error("Error in endo_settings() in New Genome.", e)
|
| 369 |
-
|
| 370 |
-
#wrapper for running jobs
|
| 371 |
-
def run_jobs_wrapper(self):
|
| 372 |
-
try:
|
| 373 |
-
self.indexes = []
|
| 374 |
-
self.job_Table.selectAll()
|
| 375 |
-
indexes = self.job_Table.selectionModel().selectedRows()
|
| 376 |
-
for index in sorted(indexes):
|
| 377 |
-
if self.job_Table.item(index.row(), 0).text() != "":
|
| 378 |
-
self.indexes.append(index.row())
|
| 379 |
-
self.run_job()
|
| 380 |
-
except Exception as e:
|
| 381 |
-
show_error("Error in run_jobs_wrapper() in New Genome.", e)
|
| 382 |
-
|
| 383 |
-
#run job in queue
|
| 384 |
-
def run_job(self):
|
| 385 |
-
try:
|
| 386 |
-
if len(self.indexes) > 0:
|
| 387 |
-
self.progressBar.setValue(0)
|
| 388 |
-
self.progress = 0
|
| 389 |
-
row_index = self.indexes[0]
|
| 390 |
-
name = self.job_Table.item(row_index, 0).text()
|
| 391 |
-
item = QtWidgets.QTableWidgetItem(name)
|
| 392 |
-
item.setTextAlignment(QtCore.Qt.AlignHCenter)
|
| 393 |
-
self.job_Table.setItem(row_index, 1, item)
|
| 394 |
-
self.job_Table.setItem(row_index, 0, QtWidgets.QTableWidgetItem(""))
|
| 395 |
-
|
| 396 |
-
def output_stdout(p):
|
| 397 |
-
line = str(p.readAll())
|
| 398 |
-
line = line[2:]
|
| 399 |
-
line = line[:len(line) - 1]
|
| 400 |
-
for lines in line.split(r"\n"):
|
| 401 |
-
lines = lines.rstrip("\n")
|
| 402 |
-
lines = lines.rstrip("\r")
|
| 403 |
-
lines = lines.rstrip(r"\n")
|
| 404 |
-
lines = lines.rstrip(r"\r")
|
| 405 |
-
lines = lines.rstrip("\r\n")
|
| 406 |
-
lines = lines.rstrip(r"\r\n")
|
| 407 |
-
if lines != "":
|
| 408 |
-
if lines.find("Number of Chromosomes/Scaffolds") != -1:
|
| 409 |
-
copy = lines
|
| 410 |
-
copy = copy.replace(" ","")
|
| 411 |
-
copy = copy[copy.find(":")+1:]
|
| 412 |
-
self.total_chrom_count = int(copy)
|
| 413 |
-
self.perc_increase = ((1 / (2 * self.total_chrom_count)) * 70)
|
| 414 |
-
self.progressBar.setValue(20)
|
| 415 |
-
self.progress = 20
|
| 416 |
-
elif lines.find("complete.") != -1:
|
| 417 |
-
self.progress += self.perc_increase
|
| 418 |
-
self.progressBar.setValue(int(self.progress))
|
| 419 |
-
elif lines.find("Processing Targets.") != -1:
|
| 420 |
-
self.progress = 70
|
| 421 |
-
self.progressBar.setValue(int(self.progress))
|
| 422 |
-
elif lines.find("Writing out uniques.") != -1:
|
| 423 |
-
self.progress = 90
|
| 424 |
-
self.progressBar.setValue(int(self.progress))
|
| 425 |
-
elif lines.find("Writing out repeats.") != -1:
|
| 426 |
-
self.progress = 95
|
| 427 |
-
self.progressBar.setValue(int(self.progress))
|
| 428 |
-
elif lines == "Finished.":
|
| 429 |
-
self.progress = 100
|
| 430 |
-
self.progressBar.setValue(int(self.progress))
|
| 431 |
-
self.output_browser.append(lines)
|
| 432 |
-
|
| 433 |
-
job_args = self.JobsQueue[row_index]
|
| 434 |
-
if platform.system() == 'Windows':
|
| 435 |
-
program = '"' + GlobalSettings.appdir + "SeqFinderFolder/Casper_Seq_Finder_Win.exe" + '" '
|
| 436 |
-
elif platform.system() == 'Linux':
|
| 437 |
-
program = '"' + GlobalSettings.appdir + "SeqFinderFolder/Casper_Seq_Finder_Lin" + '" '
|
| 438 |
-
else:
|
| 439 |
-
program = '"' + GlobalSettings.appdir + "SeqFinderFolder/Casper_Seq_Finder_Mac" + '" '
|
| 440 |
-
program += job_args
|
| 441 |
-
self.process.readyReadStandardOutput.connect(partial(output_stdout, self.process))
|
| 442 |
-
self.process.start(program)
|
| 443 |
-
else:
|
| 444 |
-
show_message(
|
| 445 |
-
fontSize=12,
|
| 446 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 447 |
-
title="No Jobs To Run",
|
| 448 |
-
message="No jobs are in the queue to run. Please add a job before running."
|
| 449 |
-
)
|
| 450 |
-
except Exception as e:
|
| 451 |
-
show_error("Error in run_job() in New Genome.", e)
|
| 452 |
-
|
| 453 |
-
#even handler for when jobs finish execution
|
| 454 |
-
def upon_process_finishing(self):
|
| 455 |
-
try:
|
| 456 |
-
row_index = self.indexes[0]
|
| 457 |
-
name = self.job_Table.item(row_index, 1).text()
|
| 458 |
-
item = QtWidgets.QTableWidgetItem(name)
|
| 459 |
-
item.setTextAlignment(QtCore.Qt.AlignHCenter)
|
| 460 |
-
self.job_Table.setItem(row_index, 2, item)
|
| 461 |
-
self.job_Table.setItem(row_index, 1, QtWidgets.QTableWidgetItem(""))
|
| 462 |
-
self.indexes.pop(0)
|
| 463 |
-
if len(self.indexes) != 0:
|
| 464 |
-
self.run_job()
|
| 465 |
-
else:
|
| 466 |
-
#prompt user if they want to analyze their new files
|
| 467 |
-
center_ui(self.goToPrompt)
|
| 468 |
-
self.goToPrompt.show()
|
| 469 |
-
self.goToPrompt.activateWindow()
|
| 470 |
-
except Exception as e:
|
| 471 |
-
show_error("Error in upon_process_finishing() in New Genome.", e)
|
| 472 |
-
|
| 473 |
-
#clear the job table
|
| 474 |
-
def clear_all(self):
|
| 475 |
-
try:
|
| 476 |
-
self.process.kill()
|
| 477 |
-
self.fin_index = 0
|
| 478 |
-
self.job_Table.clearContents()
|
| 479 |
-
self.job_Table.setRowCount(0)
|
| 480 |
-
self.JobsQueue = []
|
| 481 |
-
self.check_strings = []
|
| 482 |
-
self.output_browser.clear()
|
| 483 |
-
self.output_browser.setText("Waiting for program initiation...")
|
| 484 |
-
self.orgName.clear()
|
| 485 |
-
self.strainName.clear()
|
| 486 |
-
self.orgCode.clear()
|
| 487 |
-
self.selectedFile.clear()
|
| 488 |
-
self.selectedFile.setPlaceholderText("Selected FASTA/FNA File")
|
| 489 |
-
self.progressBar.setValue(0)
|
| 490 |
-
self.first = False
|
| 491 |
-
except Exception as e:
|
| 492 |
-
show_error("Error in clear_all() in New Genome.", e)
|
| 493 |
-
|
| 494 |
-
#reset the whole form
|
| 495 |
-
def reset(self):
|
| 496 |
-
try:
|
| 497 |
-
self.orgName.clear()
|
| 498 |
-
self.strainName.clear()
|
| 499 |
-
self.orgCode.clear()
|
| 500 |
-
self.selectedFile.clear()
|
| 501 |
-
self.selectedFile.setPlaceholderText("Selected FASTA/FNA File")
|
| 502 |
-
self.output_browser.clear()
|
| 503 |
-
self.output_browser.setText("Waiting for program initiation...")
|
| 504 |
-
self.file = ""
|
| 505 |
-
except Exception as e:
|
| 506 |
-
show_error("Error in reset() in New Genome.", e)
|
| 507 |
-
|
| 508 |
-
#event handler for user wanting to close the window
|
| 509 |
-
def closeEvent(self, event):
|
| 510 |
-
try:
|
| 511 |
-
# make sure that there are cspr files in the DB
|
| 512 |
-
file_names = os.listdir(GlobalSettings.CSPR_DB)
|
| 513 |
-
noCSPRFiles = True
|
| 514 |
-
for file in file_names:
|
| 515 |
-
if 'cspr' in file:
|
| 516 |
-
noCSPRFiles = False
|
| 517 |
-
break
|
| 518 |
-
if noCSPRFiles == True:
|
| 519 |
-
if self.exit == False:
|
| 520 |
-
msgBox = QtWidgets.QMessageBox()
|
| 521 |
-
msgBox.setStyleSheet("font: " + str(self.fontSize) + "pt 'Arial'")
|
| 522 |
-
msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
|
| 523 |
-
msgBox.setWindowTitle("No CSPR file generated")
|
| 524 |
-
msgBox.setText("No CSPR file has been generated, thus the main program cannot run. Please create a CSPR file."
|
| 525 |
-
"Alternatively, you could quit the program. Would you like to quit?")
|
| 526 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
|
| 527 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
|
| 528 |
-
msgBox.exec()
|
| 529 |
-
|
| 530 |
-
if (msgBox.result() == QtWidgets.QMessageBox.No):
|
| 531 |
-
event.ignore()
|
| 532 |
-
else:
|
| 533 |
-
event.accept()
|
| 534 |
-
else:
|
| 535 |
-
self.exit = False
|
| 536 |
-
event.accept()
|
| 537 |
-
else:
|
| 538 |
-
self.process.kill()
|
| 539 |
-
self.clear_all()
|
| 540 |
-
self.goToPrompt.hide()
|
| 541 |
-
GlobalSettings.mainWindow.fill_annotation_dropdown()
|
| 542 |
-
if GlobalSettings.mainWindow.orgChoice.currentText() != '':
|
| 543 |
-
GlobalSettings.mainWindow.orgChoice.currentIndexChanged.disconnect()
|
| 544 |
-
GlobalSettings.mainWindow.orgChoice.clear()
|
| 545 |
-
GlobalSettings.mainWindow.endoChoice.clear()
|
| 546 |
-
GlobalSettings.mainWindow.getData()
|
| 547 |
-
GlobalSettings.MTWin.launch()
|
| 548 |
-
GlobalSettings.pop_Analysis.launch()
|
| 549 |
-
|
| 550 |
-
if GlobalSettings.mainWindow.first_show == True:
|
| 551 |
-
GlobalSettings.mainWindow.first_show = False
|
| 552 |
-
GlobalSettings.mainWindow.centerUI()
|
| 553 |
-
GlobalSettings.mainWindow.show()
|
| 554 |
-
event.accept()
|
| 555 |
-
except Exception as e:
|
| 556 |
-
show_error("Error in closeEvent() in New Genome.", e)
|
| 557 |
-
|
| 558 |
-
#event handler for user wanting to go to Main once jobs complete
|
| 559 |
-
def continue_to_main(self):
|
| 560 |
-
try:
|
| 561 |
-
# make sure that there are cspr files in the DB
|
| 562 |
-
file_names = os.listdir(GlobalSettings.CSPR_DB)
|
| 563 |
-
noCSPRFiles = True
|
| 564 |
-
for file in file_names:
|
| 565 |
-
if 'cspr' in file:
|
| 566 |
-
noCSPRFiles = False
|
| 567 |
-
break
|
| 568 |
-
if noCSPRFiles == True:
|
| 569 |
-
|
| 570 |
-
msgBox = QtWidgets.QMessageBox()
|
| 571 |
-
msgBox.setStyleSheet("font: " + str(self.fontSize) + "pt 'Arial'")
|
| 572 |
-
msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
|
| 573 |
-
msgBox.setWindowTitle("No CSPR file generated")
|
| 574 |
-
msgBox.setText(
|
| 575 |
-
"No CSPR file has been generated, thus the main program cannot run. Please create a CSPR file."
|
| 576 |
-
"Alternatively, you could quit the program. Would you like to quit?")
|
| 577 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
|
| 578 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
|
| 579 |
-
msgBox.exec()
|
| 580 |
-
|
| 581 |
-
if (msgBox.result() == QtWidgets.QMessageBox.Yes):
|
| 582 |
-
self.exit = True
|
| 583 |
-
self.close()
|
| 584 |
-
else:
|
| 585 |
-
self.process.kill()
|
| 586 |
-
self.clear_all()
|
| 587 |
-
self.goToPrompt.hide()
|
| 588 |
-
GlobalSettings.mainWindow.fill_annotation_dropdown()
|
| 589 |
-
if GlobalSettings.mainWindow.orgChoice.currentText() != '':
|
| 590 |
-
GlobalSettings.mainWindow.orgChoice.currentIndexChanged.disconnect()
|
| 591 |
-
GlobalSettings.mainWindow.orgChoice.clear()
|
| 592 |
-
GlobalSettings.mainWindow.endoChoice.clear()
|
| 593 |
-
GlobalSettings.mainWindow.getData()
|
| 594 |
-
GlobalSettings.MTWin.launch()
|
| 595 |
-
GlobalSettings.pop_Analysis.launch()
|
| 596 |
-
|
| 597 |
-
# center main on current screen
|
| 598 |
-
if GlobalSettings.mainWindow.first_show == True:
|
| 599 |
-
GlobalSettings.mainWindow.first_show = False
|
| 600 |
-
center_ui(GlobalSettings.mainWindow)
|
| 601 |
-
GlobalSettings.mainWindow.show()
|
| 602 |
-
self.hide()
|
| 603 |
-
except Exception as e:
|
| 604 |
-
show_error("Error in continue_to_main() in New Genome.", e)
|
| 605 |
-
|
| 606 |
-
#event handler for user wanting to go to multi-targeting once jobs complete
|
| 607 |
-
def continue_to_MT(self):
|
| 608 |
-
try:
|
| 609 |
-
# make sure that there are cspr files in the DB
|
| 610 |
-
file_names = os.listdir(GlobalSettings.CSPR_DB)
|
| 611 |
-
noCSPRFiles = True
|
| 612 |
-
for file in file_names:
|
| 613 |
-
if 'cspr' in file:
|
| 614 |
-
noCSPRFiles = False
|
| 615 |
-
break
|
| 616 |
-
if noCSPRFiles == True:
|
| 617 |
-
|
| 618 |
-
msgBox = QtWidgets.QMessageBox()
|
| 619 |
-
msgBox.setStyleSheet("font: " + str(self.fontSize) + "pt 'Arial'")
|
| 620 |
-
msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
|
| 621 |
-
msgBox.setWindowTitle("No CSPR file generated")
|
| 622 |
-
msgBox.setText(
|
| 623 |
-
"No CSPR file has been generated, thus the main program cannot run. Please create a CSPR file."
|
| 624 |
-
"Alternatively, you could quit the program. Would you like to quit?")
|
| 625 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
|
| 626 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
|
| 627 |
-
msgBox.exec()
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
if (msgBox.result() == QtWidgets.QMessageBox.Yes):
|
| 632 |
-
self.exit = True
|
| 633 |
-
self.close()
|
| 634 |
-
|
| 635 |
-
else:
|
| 636 |
-
self.process.kill()
|
| 637 |
-
self.clear_all()
|
| 638 |
-
self.goToPrompt.hide()
|
| 639 |
-
GlobalSettings.mainWindow.fill_annotation_dropdown()
|
| 640 |
-
if GlobalSettings.mainWindow.orgChoice.currentText() != '':
|
| 641 |
-
GlobalSettings.mainWindow.orgChoice.currentIndexChanged.disconnect()
|
| 642 |
-
GlobalSettings.mainWindow.orgChoice.clear()
|
| 643 |
-
GlobalSettings.mainWindow.endoChoice.clear()
|
| 644 |
-
GlobalSettings.mainWindow.getData()
|
| 645 |
-
GlobalSettings.MTWin.launch()
|
| 646 |
-
GlobalSettings.pop_Analysis.launch()
|
| 647 |
-
|
| 648 |
-
# center multi-targeting on current screen
|
| 649 |
-
if GlobalSettings.MTWin.first_show == True:
|
| 650 |
-
GlobalSettings.MTWin.first_show = False
|
| 651 |
-
GlobalSettings.MTWin.centerUI()
|
| 652 |
-
|
| 653 |
-
GlobalSettings.MTWin.show()
|
| 654 |
-
self.hide()
|
| 655 |
-
except Exception as e:
|
| 656 |
-
show_error("Error in continue_to_MT() in New Genome.", e)
|
| 657 |
-
|
| 658 |
-
#event handler for user wanting to go to population analysis once jobs complete
|
| 659 |
-
def continue_to_pop(self):
|
| 660 |
-
try:
|
| 661 |
-
# make sure that there are cspr files in the DB
|
| 662 |
-
file_names = os.listdir(GlobalSettings.CSPR_DB)
|
| 663 |
-
noCSPRFiles = True
|
| 664 |
-
for file in file_names:
|
| 665 |
-
if 'cspr' in file:
|
| 666 |
-
noCSPRFiles = False
|
| 667 |
-
break
|
| 668 |
-
if noCSPRFiles == True:
|
| 669 |
-
|
| 670 |
-
msgBox = QtWidgets.QMessageBox()
|
| 671 |
-
msgBox.setStyleSheet("font: " + str(self.fontSize) + "pt 'Arial'")
|
| 672 |
-
msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
|
| 673 |
-
msgBox.setWindowTitle("No CSPR file generated")
|
| 674 |
-
msgBox.setText(
|
| 675 |
-
"No CSPR file has been generated, thus the main program cannot run. Please create a CSPR file."
|
| 676 |
-
"Alternatively, you could quit the program. Would you like to quit?")
|
| 677 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
|
| 678 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
|
| 679 |
-
msgBox.exec()
|
| 680 |
-
|
| 681 |
-
if (msgBox.result() == QtWidgets.QMessageBox.Yes):
|
| 682 |
-
self.exit = True
|
| 683 |
-
self.close()
|
| 684 |
-
|
| 685 |
-
else:
|
| 686 |
-
self.process.kill()
|
| 687 |
-
self.clear_all()
|
| 688 |
-
self.goToPrompt.hide()
|
| 689 |
-
GlobalSettings.mainWindow.fill_annotation_dropdown()
|
| 690 |
-
if GlobalSettings.mainWindow.orgChoice.currentText() != '':
|
| 691 |
-
GlobalSettings.mainWindow.orgChoice.currentIndexChanged.disconnect()
|
| 692 |
-
GlobalSettings.mainWindow.orgChoice.clear()
|
| 693 |
-
GlobalSettings.mainWindow.endoChoice.clear()
|
| 694 |
-
GlobalSettings.mainWindow.getData()
|
| 695 |
-
GlobalSettings.MTWin.launch()
|
| 696 |
-
GlobalSettings.pop_Analysis.launch()
|
| 697 |
-
|
| 698 |
-
if GlobalSettings.pop_Analysis.first_show == True:
|
| 699 |
-
GlobalSettings.pop_Analysis.first_show = False
|
| 700 |
-
GlobalSettings.pop_Analysis.centerUI()
|
| 701 |
-
|
| 702 |
-
GlobalSettings.pop_Analysis.show()
|
| 703 |
-
self.hide()
|
| 704 |
-
except Exception as e:
|
| 705 |
-
show_error("Error in continue_to_pop() in New Genome.", e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,130 +1,138 @@
|
|
| 1 |
from PyQt6 import QtWidgets, uic, QtGui, QtCore
|
| 2 |
-
from PyQt6.QtWidgets import QHeaderView
|
| 3 |
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
|
| 4 |
from matplotlib.figure import Figure
|
| 5 |
import mplcursors
|
| 6 |
import numpy as np
|
| 7 |
import matplotlib.patches as patches
|
| 8 |
-
from utils.ui import show_error, scale_ui
|
| 9 |
|
| 10 |
class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
|
| 11 |
def __init__(self, global_settings):
|
| 12 |
-
super(
|
| 13 |
-
self.
|
|
|
|
| 14 |
self.init_ui()
|
| 15 |
|
| 16 |
def init_ui(self):
|
| 17 |
try:
|
| 18 |
-
uic.loadUi(self.
|
| 19 |
-
self.
|
| 20 |
-
self.setWindowTitle('Population Analysis')
|
| 21 |
-
self.setup_tables()
|
| 22 |
-
self.setup_buttons()
|
| 23 |
-
self.setup_colormap()
|
| 24 |
-
self.setup_styles()
|
| 25 |
-
scale_ui(self, base_width=1920, base_height=1080, font_size=12, header_font_size=30)
|
| 26 |
except Exception as e:
|
| 27 |
-
show_error(self.
|
| 28 |
-
|
| 29 |
-
def
|
| 30 |
-
|
| 31 |
-
self.
|
| 32 |
-
self.
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
self.
|
| 36 |
-
self.
|
| 37 |
-
self.
|
| 38 |
-
self.
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
self.
|
| 42 |
-
self.
|
| 43 |
-
|
| 44 |
-
self.
|
| 45 |
-
self.
|
| 46 |
-
self.
|
| 47 |
-
self.
|
| 48 |
-
self.
|
| 49 |
-
self.
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
self.
|
| 55 |
-
self.
|
| 56 |
-
self.
|
| 57 |
-
self.
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
self.
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
self.
|
| 66 |
-
self.
|
| 67 |
-
self.
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
self.
|
| 71 |
-
self.
|
| 72 |
-
|
| 73 |
-
self.
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
def get_selected_endo(self):
|
| 89 |
-
return self.
|
| 90 |
|
| 91 |
def get_selected_organisms(self):
|
| 92 |
return [index.row() for index in self.org_Table.selectionModel().selectedRows()]
|
| 93 |
|
| 94 |
def get_selected_seeds(self):
|
| 95 |
-
return [self.
|
| 96 |
|
| 97 |
def get_seed_input(self):
|
| 98 |
return self.seed_input.text()
|
| 99 |
|
| 100 |
def get_selected_seeds_for_export(self):
|
| 101 |
-
return [item.text() for item in self.
|
| 102 |
|
| 103 |
def update_org_table(self, org_data):
|
| 104 |
-
self.
|
| 105 |
for row, (org_name, cspr_file, db_file) in enumerate(org_data):
|
| 106 |
item = QtWidgets.QTableWidgetItem(org_name)
|
| 107 |
item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignVCenter)
|
| 108 |
-
self.
|
| 109 |
-
self.
|
| 110 |
|
| 111 |
def update_shared_seeds_table(self, seed_data):
|
| 112 |
-
self.
|
| 113 |
for row, data in enumerate(seed_data):
|
|
|
|
| 114 |
for col, value in enumerate(data):
|
| 115 |
item = QtWidgets.QTableWidgetItem(str(value))
|
| 116 |
item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
| 117 |
-
self.
|
| 118 |
-
self.
|
| 119 |
|
| 120 |
def update_loc_finder_table(self, loc_data):
|
| 121 |
-
self.
|
| 122 |
for row, data in enumerate(loc_data):
|
|
|
|
| 123 |
for col, key in enumerate(['seed', 'sequence', 'organism', 'chromosome', 'location']):
|
| 124 |
item = QtWidgets.QTableWidgetItem(str(data[key]))
|
| 125 |
item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
| 126 |
-
self.
|
| 127 |
-
self.
|
| 128 |
|
| 129 |
def plot_heatmap(self, data, labels):
|
| 130 |
self.colormap_canvas.axes.clear()
|
|
@@ -159,28 +167,35 @@ class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
|
|
| 159 |
self.colormap_canvas.draw()
|
| 160 |
|
| 161 |
def clear_shared_seeds_table(self):
|
| 162 |
-
self.
|
| 163 |
|
| 164 |
def clear_loc_finder_table(self):
|
| 165 |
self.loc_finder_table.setRowCount(0)
|
| 166 |
|
| 167 |
-
def show_loading_window(self, value):
|
| 168 |
-
self.loading_window.loading_bar.setValue(value)
|
| 169 |
-
self.loading_window.show()
|
| 170 |
-
|
| 171 |
-
def hide_loading_window(self):
|
| 172 |
-
self.loading_window.hide()
|
| 173 |
-
|
| 174 |
-
def update_loading_window(self, value, text):
|
| 175 |
-
self.loading_window.loading_bar.setValue(value)
|
| 176 |
-
self.loading_window.info_label.setText(text)
|
| 177 |
-
|
| 178 |
def update_endo_dropdown(self, endos):
|
| 179 |
-
|
| 180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
|
| 182 |
def sort_table2(self, column):
|
| 183 |
-
self.
|
| 184 |
|
| 185 |
def sort_loc_finder_table(self, column):
|
| 186 |
self.loc_finder_table.sortItems(column)
|
|
@@ -194,12 +209,3 @@ class MplCanvas(FigureCanvasQTAgg):
|
|
| 194 |
super(MplCanvas, self).__init__(fig)
|
| 195 |
except Exception as e:
|
| 196 |
show_error("Error initializing MplCanvas class in population analysis.", e)
|
| 197 |
-
|
| 198 |
-
class LoadingWindow(QtWidgets.QMainWindow):
|
| 199 |
-
def __init__(self, global_settings):
|
| 200 |
-
super(LoadingWindow, self).__init__()
|
| 201 |
-
uic.loadUi(global_settings.get_ui_dir() + "/loading_data_form.ui", self)
|
| 202 |
-
self.loading_bar.setValue(0)
|
| 203 |
-
self.setWindowTitle("Loading Data")
|
| 204 |
-
self.setWindowIcon(QtGui.QIcon(global_settings.get_assets_dir() + "cas9image.ico"))
|
| 205 |
-
scale_ui(self, base_width=1920, base_height=1080, font_size=12, header_font_size=30, custom_scale_width=450, custom_scale_height=125)
|
|
|
|
| 1 |
from PyQt6 import QtWidgets, uic, QtGui, QtCore
|
| 2 |
+
from PyQt6.QtWidgets import QHeaderView, QAbstractItemView
|
| 3 |
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
|
| 4 |
from matplotlib.figure import Figure
|
| 5 |
import mplcursors
|
| 6 |
import numpy as np
|
| 7 |
import matplotlib.patches as patches
|
| 8 |
+
from utils.ui import show_error, scale_ui
|
| 9 |
|
| 10 |
class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
|
| 11 |
def __init__(self, global_settings):
|
| 12 |
+
super().__init__()
|
| 13 |
+
self.settings = global_settings
|
| 14 |
+
self.logger = self.settings.get_logger()
|
| 15 |
self.init_ui()
|
| 16 |
|
| 17 |
def init_ui(self):
|
| 18 |
try:
|
| 19 |
+
uic.loadUi(self.settings.get_ui_dir_path() + '/population_analysis.ui', self)
|
| 20 |
+
self._init_ui_components()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
except Exception as e:
|
| 22 |
+
show_error(self.settings, "Error initializing PopulationAnalysisWindowView", str(e))
|
| 23 |
+
|
| 24 |
+
def _init_ui_components(self):
|
| 25 |
+
self._init_grpSelectOrganisms()
|
| 26 |
+
self._init_grpSeedAnalysis()
|
| 27 |
+
# self._init_colormap()
|
| 28 |
+
|
| 29 |
+
def _init_grpSelectOrganisms(self):
|
| 30 |
+
self.combo_box_endonuclease = self._find_widget('cmbEndonuclease', QtWidgets.QComboBox)
|
| 31 |
+
print(self.combo_box_endonuclease)
|
| 32 |
+
self.table_organism = self._find_widget('tblOrganism', QtWidgets.QTableWidget)
|
| 33 |
+
self.push_button_analyze_organism = self._find_widget('pbtnAnalyzeOrganism', QtWidgets.QPushButton)
|
| 34 |
+
|
| 35 |
+
self.tab_widget_shared_seeds_heatmap = self._find_widget('tabsSharedSeedHeatmap', QtWidgets.QTabWidget)
|
| 36 |
+
self.tab_shared_seed_heatmap = self._find_widget('tabSharedSeedHeatmap', QtWidgets.QWidget)
|
| 37 |
+
self.heatmap_seed = self._find_widget('heatmapSeed', QtWidgets.QWidget)
|
| 38 |
+
|
| 39 |
+
self.table_organism.setColumnCount(1)
|
| 40 |
+
self.table_organism.setShowGrid(False)
|
| 41 |
+
self.table_organism.setHorizontalHeaderLabels(["Organism"])
|
| 42 |
+
self.table_organism.horizontalHeader().setSectionsClickable(True)
|
| 43 |
+
self.table_organism.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
| 44 |
+
self.table_organism.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
| 45 |
+
self.table_organism.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
| 46 |
+
self.table_organism.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
|
| 47 |
+
|
| 48 |
+
def _init_grpSeedAnalysis(self):
|
| 49 |
+
self.line_edit_seed = self._find_widget('ledSeed', QtWidgets.QLineEdit)
|
| 50 |
+
self.push_button_query_seed = self._find_widget('pbtnQuerySeed', QtWidgets.QPushButton)
|
| 51 |
+
self.push_button_clear_seeds = self._find_widget('pbtnClearSeeds', QtWidgets.QPushButton)
|
| 52 |
+
self.table_seed = self._find_widget('tblSeed', QtWidgets.QTableWidget)
|
| 53 |
+
|
| 54 |
+
self.table_seed.setColumnCount(9)
|
| 55 |
+
self.table_seed.setShowGrid(False)
|
| 56 |
+
self.table_seed.setHorizontalHeaderLabels([
|
| 57 |
+
"Seed", "% Coverage", "Total Repeats", "Avg. Repeats/Scaffold",
|
| 58 |
+
"Consensus Sequence", "% Consensus", "Score", "PAM", "Strand"
|
| 59 |
+
])
|
| 60 |
+
self.table_seed.horizontalHeader().setSectionsClickable(True)
|
| 61 |
+
self.table_seed.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
| 62 |
+
self.table_seed.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
| 63 |
+
self.table_seed.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
|
| 64 |
+
|
| 65 |
+
self.push_button_find_locations = self._find_widget('pbtnFindLocations', QtWidgets.QPushButton)
|
| 66 |
+
self.push_button_clear_locations = self._find_widget('pbtnClearLocations', QtWidgets.QPushButton)
|
| 67 |
+
|
| 68 |
+
self.table_locations = self._find_widget('tblLocation', QtWidgets.QTableWidget)
|
| 69 |
+
|
| 70 |
+
self.table_locations.setColumnCount(5)
|
| 71 |
+
self.table_locations.setShowGrid(False)
|
| 72 |
+
self.table_locations.setHorizontalHeaderLabels([
|
| 73 |
+
"Seed ID", "Sequence", "Organism", "Scaffold", "Location"
|
| 74 |
+
])
|
| 75 |
+
self.table_locations.horizontalHeader().setSectionsClickable(True)
|
| 76 |
+
self.table_locations.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
|
| 77 |
+
self.table_locations.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
| 78 |
+
|
| 79 |
+
# def _init_colormap(self):
|
| 80 |
+
# self.colormap_figure = self._find_widget('wgtColormap', QtWidgets.QWidget)
|
| 81 |
+
# if self.colormap_figure:
|
| 82 |
+
# self.colormap_layout = QtWidgets.QVBoxLayout()
|
| 83 |
+
# self.colormap_layout.setContentsMargins(0, 0, 0, 0)
|
| 84 |
+
# self.colormap_canvas = MplCanvas(self)
|
| 85 |
+
# self.colormap_layout.addWidget(self.colormap_canvas)
|
| 86 |
+
# self.colormap_figure.setLayout(self.colormap_layout)
|
| 87 |
+
|
| 88 |
+
def _find_widget(self, name: str, widget_type: type) -> QtWidgets.QWidget:
|
| 89 |
+
widget = self.findChild(widget_type, name)
|
| 90 |
+
if widget is None:
|
| 91 |
+
self.logger.warning(f"Widget '{name}' not found in UI file.")
|
| 92 |
+
return widget
|
| 93 |
|
| 94 |
def get_selected_endo(self):
|
| 95 |
+
return self.combo_box_endonuclease.currentText()
|
| 96 |
|
| 97 |
def get_selected_organisms(self):
|
| 98 |
return [index.row() for index in self.org_Table.selectionModel().selectedRows()]
|
| 99 |
|
| 100 |
def get_selected_seeds(self):
|
| 101 |
+
return [self.table_seed.item(index.row(), 0).text() for index in self.table_seed.selectionModel().selectedRows()]
|
| 102 |
|
| 103 |
def get_seed_input(self):
|
| 104 |
return self.seed_input.text()
|
| 105 |
|
| 106 |
def get_selected_seeds_for_export(self):
|
| 107 |
+
return [item.text() for item in self.table_seed.selectedItems() if item.column() == 0]
|
| 108 |
|
| 109 |
def update_org_table(self, org_data):
|
| 110 |
+
self.table_organism.setRowCount(len(org_data))
|
| 111 |
for row, (org_name, cspr_file, db_file) in enumerate(org_data):
|
| 112 |
item = QtWidgets.QTableWidgetItem(org_name)
|
| 113 |
item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignVCenter)
|
| 114 |
+
self.table_organism.setItem(row, 0, item)
|
| 115 |
+
self.table_organism.resizeColumnsToContents()
|
| 116 |
|
| 117 |
def update_shared_seeds_table(self, seed_data):
|
| 118 |
+
self.table_seed.setRowCount(len(seed_data))
|
| 119 |
for row, data in enumerate(seed_data):
|
| 120 |
+
print(data)
|
| 121 |
for col, value in enumerate(data):
|
| 122 |
item = QtWidgets.QTableWidgetItem(str(value))
|
| 123 |
item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
| 124 |
+
self.table_seed.setItem(row, col, item)
|
| 125 |
+
self.table_seed.resizeColumnsToContents()
|
| 126 |
|
| 127 |
def update_loc_finder_table(self, loc_data):
|
| 128 |
+
self.table_locations.setRowCount(len(loc_data))
|
| 129 |
for row, data in enumerate(loc_data):
|
| 130 |
+
print(data)
|
| 131 |
for col, key in enumerate(['seed', 'sequence', 'organism', 'chromosome', 'location']):
|
| 132 |
item = QtWidgets.QTableWidgetItem(str(data[key]))
|
| 133 |
item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
| 134 |
+
self.table_locations.setItem(row, col, item)
|
| 135 |
+
self.table_locations.resizeColumnsToContents()
|
| 136 |
|
| 137 |
def plot_heatmap(self, data, labels):
|
| 138 |
self.colormap_canvas.axes.clear()
|
|
|
|
| 167 |
self.colormap_canvas.draw()
|
| 168 |
|
| 169 |
def clear_shared_seeds_table(self):
|
| 170 |
+
self.table_seed.setRowCount(0)
|
| 171 |
|
| 172 |
def clear_loc_finder_table(self):
|
| 173 |
self.loc_finder_table.setRowCount(0)
|
| 174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
def update_endo_dropdown(self, endos):
|
| 176 |
+
"""Update the endonuclease dropdown with the provided options"""
|
| 177 |
+
try:
|
| 178 |
+
self.logger.info("Starting update_endo_dropdown")
|
| 179 |
+
self.logger.debug(f"Received endos: {endos}")
|
| 180 |
+
|
| 181 |
+
print(self.combo_box_endonuclease)
|
| 182 |
+
|
| 183 |
+
# if not self.combo_box_endonuclease:
|
| 184 |
+
# self.logger.error("combo_box_endonuclease is None")
|
| 185 |
+
# return
|
| 186 |
+
|
| 187 |
+
self.combo_box_endonuclease.clear()
|
| 188 |
+
self.combo_box_endonuclease.addItems(endos)
|
| 189 |
+
|
| 190 |
+
self.logger.info(f"Updated endonuclease dropdown with {len(endos)} options")
|
| 191 |
+
self.logger.debug(f"Current items in dropdown: {[self.combo_box_endonuclease.itemText(i) for i in range(self.combo_box_endonuclease.count())]}")
|
| 192 |
+
except Exception as e:
|
| 193 |
+
self.logger.error(f"Error updating endonuclease dropdown: {str(e)}")
|
| 194 |
+
self.logger.exception("Full traceback:")
|
| 195 |
+
show_error(self.settings, "Error updating endonuclease dropdown", str(e))
|
| 196 |
|
| 197 |
def sort_table2(self, column):
|
| 198 |
+
self.table_seed.sortItems(column)
|
| 199 |
|
| 200 |
def sort_loc_finder_table(self, column):
|
| 201 |
self.loc_finder_table.sortItems(column)
|
|
|
|
| 209 |
super(MplCanvas, self).__init__(fig)
|
| 210 |
except Exception as e:
|
| 211 |
show_error("Error initializing MplCanvas class in population analysis.", e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -61,24 +61,63 @@ class ViewTargetsView(QtWidgets.QMainWindow):
|
|
| 61 |
return widget
|
| 62 |
|
| 63 |
def display_targets_in_table(self, targets):
|
|
|
|
| 64 |
self.table_targets.setRowCount(len(targets))
|
|
|
|
| 65 |
for row, target in enumerate(targets):
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
|
|
|
| 74 |
details_button = QtWidgets.QPushButton("Details")
|
| 75 |
self.table_targets.setCellWidget(row, 7, details_button)
|
| 76 |
-
|
| 77 |
self.table_targets.resizeColumnsToContents()
|
| 78 |
|
| 79 |
def get_selected_targets(self):
|
|
|
|
| 80 |
selected_rows = set(index.row() for index in self.table_targets.selectedIndexes())
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
def get_row_data(self, row):
|
| 84 |
return {
|
|
|
|
| 61 |
return widget
|
| 62 |
|
| 63 |
def display_targets_in_table(self, targets):
|
| 64 |
+
"""Display targets in table with all data"""
|
| 65 |
self.table_targets.setRowCount(len(targets))
|
| 66 |
+
|
| 67 |
for row, target in enumerate(targets):
|
| 68 |
+
# Handle tuple format (location, sequence, pam, score, strand, endonuclease)
|
| 69 |
+
if isinstance(target, tuple):
|
| 70 |
+
self.table_targets.setItem(row, 0, QTableWidgetItem(str(target[0]))) # Location
|
| 71 |
+
self.table_targets.setItem(row, 1, QTableWidgetItem(str(target[5]))) # Endonuclease
|
| 72 |
+
self.table_targets.setItem(row, 2, QTableWidgetItem(str(target[1]))) # Sequence
|
| 73 |
+
self.table_targets.setItem(row, 3, QTableWidgetItem(str(target[4]))) # Strand
|
| 74 |
+
self.table_targets.setItem(row, 4, QTableWidgetItem(str(target[2]))) # PAM
|
| 75 |
+
self.table_targets.setItem(row, 5, QTableWidgetItem(str(target[3]))) # Score
|
| 76 |
+
self.table_targets.setItem(row, 6, QTableWidgetItem("--.--")) # Off-Target placeholder
|
| 77 |
+
# Handle dictionary format
|
| 78 |
+
else:
|
| 79 |
+
self.table_targets.setItem(row, 0, QTableWidgetItem(str(target['location'])))
|
| 80 |
+
self.table_targets.setItem(row, 1, QTableWidgetItem(str(target['endonuclease'])))
|
| 81 |
+
self.table_targets.setItem(row, 2, QTableWidgetItem(str(target['sequence'])))
|
| 82 |
+
self.table_targets.setItem(row, 3, QTableWidgetItem(str(target['strand'])))
|
| 83 |
+
self.table_targets.setItem(row, 4, QTableWidgetItem(str(target['pam'])))
|
| 84 |
+
self.table_targets.setItem(row, 5, QTableWidgetItem(str(target['score'])))
|
| 85 |
+
self.table_targets.setItem(row, 6, QTableWidgetItem("--.--"))
|
| 86 |
|
| 87 |
+
# Add details button
|
| 88 |
details_button = QtWidgets.QPushButton("Details")
|
| 89 |
self.table_targets.setCellWidget(row, 7, details_button)
|
| 90 |
+
|
| 91 |
self.table_targets.resizeColumnsToContents()
|
| 92 |
|
| 93 |
def get_selected_targets(self):
|
| 94 |
+
"""Get selected targets with all necessary data"""
|
| 95 |
selected_rows = set(index.row() for index in self.table_targets.selectedIndexes())
|
| 96 |
+
selected_targets = []
|
| 97 |
+
|
| 98 |
+
# Get column indices once
|
| 99 |
+
columns = {
|
| 100 |
+
'location': 0,
|
| 101 |
+
'endonuclease': 1,
|
| 102 |
+
'sequence': 2, # Make sure to get the sequence
|
| 103 |
+
'strand': 3,
|
| 104 |
+
'pam': 4,
|
| 105 |
+
'score': 5,
|
| 106 |
+
'off_target': 6
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
for row in selected_rows:
|
| 110 |
+
target = {
|
| 111 |
+
'location': self.table_targets.item(row, columns['location']).text(),
|
| 112 |
+
'endonuclease': self.table_targets.item(row, columns['endonuclease']).text(),
|
| 113 |
+
'sequence': self.table_targets.item(row, columns['sequence']).text(), # Get sequence
|
| 114 |
+
'strand': self.table_targets.item(row, columns['strand']).text(),
|
| 115 |
+
'pam': self.table_targets.item(row, columns['pam']).text(),
|
| 116 |
+
'score': self.table_targets.item(row, columns['score']).text(),
|
| 117 |
+
'off_target': self.table_targets.item(row, columns['off_target']).text()
|
| 118 |
+
}
|
| 119 |
+
selected_targets.append(target)
|
| 120 |
+
return selected_targets
|
| 121 |
|
| 122 |
def get_row_data(self, row):
|
| 123 |
return {
|
|
@@ -1,73 +0,0 @@
|
|
| 1 |
-
import models.GlobalSettings as GlobalSettings
|
| 2 |
-
import os
|
| 3 |
-
from PyQt5 import QtWidgets, Qt, uic
|
| 4 |
-
import traceback
|
| 5 |
-
import math
|
| 6 |
-
from utils.ui import show_error, scale_ui
|
| 7 |
-
|
| 8 |
-
logger = GlobalSettings.logger
|
| 9 |
-
|
| 10 |
-
###########################################################
|
| 11 |
-
# closingWindow: this class is a little window where the user can select which files they want to delete
|
| 12 |
-
# Once they hit 'submit' it will delete all of the files selected, and close the program.
|
| 13 |
-
# If no files are selected, the program closes and no files are deleted
|
| 14 |
-
# Inputs are taking from the user (selecting files to delete and hitting submit), as well as GlobalSettings for the files in CSPR_DB
|
| 15 |
-
# Outputs are the files are deleting, and the program is closed
|
| 16 |
-
###########################################################
|
| 17 |
-
class closingWindow(QtWidgets.QMainWindow):
|
| 18 |
-
def __init__(self):
|
| 19 |
-
try:
|
| 20 |
-
super(closingWindow, self).__init__()
|
| 21 |
-
uic.loadUi(GlobalSettings.appdir + "ui/closing_window.ui", self)
|
| 22 |
-
self.setWindowTitle("Delete Files")
|
| 23 |
-
self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
|
| 24 |
-
|
| 25 |
-
# Button
|
| 26 |
-
self.submit_button.clicked.connect(self.submit_and_close)
|
| 27 |
-
|
| 28 |
-
# Table
|
| 29 |
-
self.files_table.setColumnCount(1)
|
| 30 |
-
self.files_table.setShowGrid(True)
|
| 31 |
-
self.files_table.setHorizontalHeaderLabels("File Name;".split(";"))
|
| 32 |
-
self.files_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
| 33 |
-
self.files_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
| 34 |
-
self.files_table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
scale_ui(self, custom_scale_width=400, custom_scale_height=300)
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
except Exception as e:
|
| 41 |
-
show_error("Error initializing closingWindow class.", e)
|
| 42 |
-
|
| 43 |
-
# this function will delete selected files, and then close the program
|
| 44 |
-
def submit_and_close(self):
|
| 45 |
-
try:
|
| 46 |
-
# loop through the whole table
|
| 47 |
-
for i in range(self.files_table.rowCount()):
|
| 48 |
-
tabWidget = self.files_table.item(i, 0)
|
| 49 |
-
|
| 50 |
-
# if that specific tab is selected, delete it. otherwise do nothing
|
| 51 |
-
if tabWidget.isSelected():
|
| 52 |
-
os.remove(tabWidget.text())
|
| 53 |
-
self.close()
|
| 54 |
-
except Exception as e:
|
| 55 |
-
show_error("Error in sumbit_and_close() in closing window.", e)
|
| 56 |
-
|
| 57 |
-
# this function gets all of the files from the CSPR_DB and puts them all into the table
|
| 58 |
-
def get_files(self):
|
| 59 |
-
try:
|
| 60 |
-
loopCount = 0
|
| 61 |
-
# get the file names from CSPR_DB
|
| 62 |
-
files_names = os.listdir(GlobalSettings.CSPR_DB)
|
| 63 |
-
files_names.sort(key=str.lower)
|
| 64 |
-
self.files_table.setRowCount(len(files_names))
|
| 65 |
-
|
| 66 |
-
# loop through and add them to the table
|
| 67 |
-
for file in files_names:
|
| 68 |
-
tabWidget = QtWidgets.QTableWidgetItem(file)
|
| 69 |
-
self.files_table.setItem(loopCount, 0, tabWidget)
|
| 70 |
-
loopCount += 1
|
| 71 |
-
self.files_table.resizeColumnsToContents()
|
| 72 |
-
except Exception as e:
|
| 73 |
-
show_error("Error in get_files() in closing window.", e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,259 +0,0 @@
|
|
| 1 |
-
import models.GlobalSettings as GlobalSettings
|
| 2 |
-
from utils.sequence_utils import get_table_headers
|
| 3 |
-
import os
|
| 4 |
-
from PyQt5 import QtWidgets, Qt, uic, QtCore, QtGui
|
| 5 |
-
import platform
|
| 6 |
-
import traceback
|
| 7 |
-
import math
|
| 8 |
-
from utils.ui import show_message, show_error, scale_ui, center_ui
|
| 9 |
-
|
| 10 |
-
logger = GlobalSettings.logger
|
| 11 |
-
|
| 12 |
-
# This class opens a window for the user to select where they want the CSV file exported to, and the name of the file
|
| 13 |
-
# It takes the highlighted data from the Results page, and creates a CSV file from that
|
| 14 |
-
class export_tool(QtWidgets.QMainWindow):
|
| 15 |
-
def __init__(self):
|
| 16 |
-
try:
|
| 17 |
-
super(export_tool, self).__init__()
|
| 18 |
-
uic.loadUi(GlobalSettings.appdir + 'ui/export_tool.ui', self)
|
| 19 |
-
self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
|
| 20 |
-
|
| 21 |
-
self.browse_button.clicked.connect(self.browseForFolder)
|
| 22 |
-
self.cancel_button.clicked.connect(self.cancel_function)
|
| 23 |
-
self.export_button.clicked.connect(self.export_function)
|
| 24 |
-
|
| 25 |
-
# Set up validators for input fields:
|
| 26 |
-
reg_ex = QtCore.QRegExp("[^,]+") # No commas
|
| 27 |
-
input_validator = QtGui.QRegExpValidator(reg_ex, self)
|
| 28 |
-
self.leading_seq.setValidator(input_validator)
|
| 29 |
-
self.trailing_seq.setValidator(input_validator)
|
| 30 |
-
|
| 31 |
-
# GroupBox styling
|
| 32 |
-
groupbox_style = """
|
| 33 |
-
QGroupBox:title{subcontrol-origin: margin;
|
| 34 |
-
left: 10px;
|
| 35 |
-
padding: 0 5px 0 5px;}
|
| 36 |
-
QGroupBox#gRNA_Options{border: 2px solid rgb(111,181,110);
|
| 37 |
-
border-radius: 9px;
|
| 38 |
-
margin-top: 10px;
|
| 39 |
-
font: bold 14pt 'Arial';} """
|
| 40 |
-
self.gRNA_Options.setStyleSheet(groupbox_style)
|
| 41 |
-
|
| 42 |
-
self.location = self.fileLocation_line_edit.text()
|
| 43 |
-
self.selected_table_items = []
|
| 44 |
-
self.window = ""
|
| 45 |
-
self.num_columns = []
|
| 46 |
-
self.locus_tag = False
|
| 47 |
-
self.gene_name = False
|
| 48 |
-
|
| 49 |
-
self.setWindowTitle("Export to CSV")
|
| 50 |
-
scale_ui(self, custom_scale_width=650, custom_scale_height=200)
|
| 51 |
-
|
| 52 |
-
except Exception as e:
|
| 53 |
-
show_error("Error initializing export_tool class.", e)
|
| 54 |
-
|
| 55 |
-
# launch function. Called in Results.
|
| 56 |
-
# parameter expect: a list of the items selected from the window.
|
| 57 |
-
def launch(self, select_items, window):
|
| 58 |
-
try:
|
| 59 |
-
if platform.system() == "Windows":
|
| 60 |
-
self.fileLocation_line_edit.setText(GlobalSettings.CSPR_DB + "\\")
|
| 61 |
-
else:
|
| 62 |
-
self.fileLocation_line_edit.setText(GlobalSettings.CSPR_DB + "/")
|
| 63 |
-
self.selected_table_items = select_items
|
| 64 |
-
self.window = window
|
| 65 |
-
center_ui(self)
|
| 66 |
-
self.show()
|
| 67 |
-
self.activateWindow()
|
| 68 |
-
except Exception as e:
|
| 69 |
-
show_error("Error in launch() in export_tool.", e)
|
| 70 |
-
|
| 71 |
-
# Takes the path and file name and combines them
|
| 72 |
-
# Writes the header line, as well as ever line selected to that file
|
| 73 |
-
# calls the cancel function when it's done
|
| 74 |
-
def export_function(self):
|
| 75 |
-
try:
|
| 76 |
-
delim = self.delimBox.currentText()
|
| 77 |
-
# get the full path ( path and file name)
|
| 78 |
-
file_name = self.filename_line_edit.text()
|
| 79 |
-
if file_name == "":
|
| 80 |
-
file_name = "exported_gRNAs"
|
| 81 |
-
self.location = self.fileLocation_line_edit.text()
|
| 82 |
-
full_path = ""
|
| 83 |
-
if '.' in file_name: # If user added the file extension...
|
| 84 |
-
full_path = self.location + file_name
|
| 85 |
-
else:
|
| 86 |
-
if delim == ",":
|
| 87 |
-
full_path = self.location + file_name + '.csv'
|
| 88 |
-
elif delim == r"\t":
|
| 89 |
-
delim = "\t"
|
| 90 |
-
full_path = self.location + file_name + '.tsv'
|
| 91 |
-
else:
|
| 92 |
-
full_path = self.location + file_name + '.txt'
|
| 93 |
-
try:
|
| 94 |
-
output_data = open(full_path, 'w')
|
| 95 |
-
""" Write the table headers """
|
| 96 |
-
if self.window == "mt": ###Change headers for multitargeting table export
|
| 97 |
-
headers = get_table_headers(GlobalSettings.MTWin.table)
|
| 98 |
-
num_cols = len(headers) # Calculate the number of columns based on the headers list above
|
| 99 |
-
insertion_index = headers.index("% Consensus")
|
| 100 |
-
headers.insert(insertion_index, "Full Sequence")
|
| 101 |
-
output_data.write(delim.join(headers)+"\n")
|
| 102 |
-
elif self.window == "pa":
|
| 103 |
-
headers = get_table_headers(GlobalSettings.pop_Analysis.table2)
|
| 104 |
-
num_cols = len(headers) # Calculate the number of columns based on the headers list above
|
| 105 |
-
insertion_index = headers.index("% Consensus")
|
| 106 |
-
headers.insert(insertion_index, "Full Sequence")
|
| 107 |
-
output_data.write(delim.join(headers)+"\n")
|
| 108 |
-
else: ###Change headers for view results export
|
| 109 |
-
headers = get_table_headers(GlobalSettings.mainWindow.Results.targetTable)
|
| 110 |
-
headers.remove("Details") # For some reason, the details column doesn't carry any "items"
|
| 111 |
-
num_cols = len(headers) # Calculate the number of columns based on the headers list above
|
| 112 |
-
insertion_index = headers.index("Strand")
|
| 113 |
-
headers.insert(insertion_index, "Full Sequence")
|
| 114 |
-
|
| 115 |
-
if GlobalSettings.mainWindow.radioButton_Gene.isChecked(): # If the user chose to search via Feature
|
| 116 |
-
tmp = GlobalSettings.mainWindow.Results.comboBoxGene.currentText().split(":") # Check to see if the locus tag was found for the current gene
|
| 117 |
-
if len(tmp) > 1: # If locus tag exists for gene, include in output
|
| 118 |
-
headers.extend(["Locus_Tag","Gene_Name"])
|
| 119 |
-
output_data.write(delim.join(headers)+"\n")
|
| 120 |
-
self.locus_tag = True
|
| 121 |
-
self.gene_name = True
|
| 122 |
-
else: # If locus tag does not exist for gene, only include the gene name
|
| 123 |
-
headers.append("Gene_Name")
|
| 124 |
-
output_data.write(delim.join(headers)+"\n")
|
| 125 |
-
self.gene_name = True
|
| 126 |
-
self.locus_tag = False
|
| 127 |
-
else: # If user searched by sequence or position, don't include locus tag or gene name
|
| 128 |
-
output_data.write(delim.join(headers)+"\n")
|
| 129 |
-
self.gene_name = False
|
| 130 |
-
self.locus_tag = False
|
| 131 |
-
|
| 132 |
-
""" Write the data out """
|
| 133 |
-
tmp_list = []
|
| 134 |
-
if self.locus_tag: #If the user is exporting data from VT and locus tag exists for current gene
|
| 135 |
-
tmp = GlobalSettings.mainWindow.Results.comboBoxGene.currentText().split(":") # Get the locus tag
|
| 136 |
-
locus_tag = str(tmp[0].strip())
|
| 137 |
-
gene_name = str(tmp[-1].strip())
|
| 138 |
-
seq_index = headers.index("Sequence") # Get the gene name
|
| 139 |
-
it = 0
|
| 140 |
-
for i, item in enumerate(self.selected_table_items): # Loop through all the items in the View Targets table
|
| 141 |
-
if (i+1) % num_cols == 0:
|
| 142 |
-
tmp_list.append(item.text())
|
| 143 |
-
tmp_list.append(locus_tag)
|
| 144 |
-
tmp_list.append(gene_name)
|
| 145 |
-
output_data.write(delim.join(tmp_list)+"\n") # Write data out
|
| 146 |
-
tmp_list.clear() # Reset list
|
| 147 |
-
it = 0 # Reset iterator
|
| 148 |
-
elif it == seq_index:
|
| 149 |
-
tmp_list.append(item.text())
|
| 150 |
-
tmp_list.append(self.leading_seq.text().strip() + item.text() + self.trailing_seq.text().strip()) #5' Leader + gRNA + 3' Trailer
|
| 151 |
-
it += 1
|
| 152 |
-
else:
|
| 153 |
-
tmp_list.append(item.text())
|
| 154 |
-
it += 1
|
| 155 |
-
elif self.gene_name: #If the user is exporting data from VT and locus tag doesn't exist for current gene
|
| 156 |
-
gene_name = str(GlobalSettings.mainWindow.Results.comboBoxGene.currentText().strip()) # Get the locus tag
|
| 157 |
-
seq_index = headers.index("Sequence") # Get the gene name
|
| 158 |
-
it = 0
|
| 159 |
-
for i, item in enumerate(self.selected_table_items): # Loop through all the items in the View Targets table
|
| 160 |
-
if (i+1) % num_cols == 0:
|
| 161 |
-
tmp_list.append(item.text())
|
| 162 |
-
tmp_list.append(gene_name)
|
| 163 |
-
output_data.write(delim.join(tmp_list)+"\n")
|
| 164 |
-
tmp_list.clear()
|
| 165 |
-
it = 0 # Reset iterator
|
| 166 |
-
elif it == seq_index:
|
| 167 |
-
tmp_list.append(item.text())
|
| 168 |
-
tmp_list.append(self.leading_seq.text().strip() + item.text() + self.trailing_seq.text().strip()) #5' Leader + gRNA + 3' Trailer
|
| 169 |
-
it += 1
|
| 170 |
-
else:
|
| 171 |
-
tmp_list.append(item.text())
|
| 172 |
-
it += 1
|
| 173 |
-
elif self.window in ["mt", "pa"]: #If the user is exporting data from multitargeting
|
| 174 |
-
seq_index = headers.index("Consensus Sequence")
|
| 175 |
-
it = 0
|
| 176 |
-
for i, item in enumerate(self.selected_table_items): # Loop through all the items in the View Targets table
|
| 177 |
-
if (i+1) % num_cols == 0:
|
| 178 |
-
tmp_list.append(item.text())
|
| 179 |
-
output_data.write(str(delim.join(tmp_list))+"\n")
|
| 180 |
-
tmp_list.clear()
|
| 181 |
-
it = 0 # Reset iterator
|
| 182 |
-
elif it == seq_index:
|
| 183 |
-
tmp_list.append(item.text())
|
| 184 |
-
tmp_list.append(self.leading_seq.text().strip() + item.text() + self.trailing_seq.text().strip()) #5' Leader + gRNA + 3' Trailer
|
| 185 |
-
it += 1
|
| 186 |
-
else:
|
| 187 |
-
tmp_list.append(item.text())
|
| 188 |
-
it += 1
|
| 189 |
-
else: #If the user is exporting data from View Targets but is not using Feature search
|
| 190 |
-
seq_index = headers.index("Sequence") # Get the gene name
|
| 191 |
-
it = 0
|
| 192 |
-
for i, item in enumerate(self.selected_table_items): # Loop through all the items in the View Targets table
|
| 193 |
-
if (i+1) % num_cols == 0:
|
| 194 |
-
tmp_list.append(item.text())
|
| 195 |
-
output_data.write(delim.join(tmp_list)+"\n")
|
| 196 |
-
tmp_list.clear()
|
| 197 |
-
it = 0 # Reset iterator
|
| 198 |
-
elif it == seq_index:
|
| 199 |
-
tmp_list.append(item.text())
|
| 200 |
-
tmp_list.append(self.leading_seq.text().strip() + item.text() + self.trailing_seq.text().strip()) #5' Leader + gRNA + 3' Trailer
|
| 201 |
-
it += 1
|
| 202 |
-
else:
|
| 203 |
-
tmp_list.append(item.text())
|
| 204 |
-
it += 1
|
| 205 |
-
output_data.close()
|
| 206 |
-
except PermissionError:
|
| 207 |
-
show_error("This file cannot be opened. Please make sure that the file is not opened elsewhere and try again.", e)
|
| 208 |
-
return
|
| 209 |
-
|
| 210 |
-
except Exception as e:
|
| 211 |
-
show_error("Error in export_function() in export_tool.", e)
|
| 212 |
-
return
|
| 213 |
-
|
| 214 |
-
""" Print "finished" message """
|
| 215 |
-
show_message(
|
| 216 |
-
fontSize=12,
|
| 217 |
-
icon=QtWidgets.QMessageBox.Icon.Information,
|
| 218 |
-
title="Export Complete",
|
| 219 |
-
message=f"Export to {full_path} was successful."
|
| 220 |
-
)
|
| 221 |
-
|
| 222 |
-
# close the window
|
| 223 |
-
self.cancel_function()
|
| 224 |
-
except Exception as e:
|
| 225 |
-
show_error("Error in export_function() in export_tool.", e)
|
| 226 |
-
|
| 227 |
-
# Resets everything to the init funciton
|
| 228 |
-
# then closes the window
|
| 229 |
-
def cancel_function(self):
|
| 230 |
-
try:
|
| 231 |
-
if platform.system() == "Windows":
|
| 232 |
-
self.fileLocation_line_edit.setText(GlobalSettings.CSPR_DB + "\\")
|
| 233 |
-
else:
|
| 234 |
-
self.fileLocation_line_edit.setText(GlobalSettings.CSPR_DB + "/")
|
| 235 |
-
self.filename_line_edit.setText("")
|
| 236 |
-
self.location = ""
|
| 237 |
-
self.hide()
|
| 238 |
-
except Exception as e:
|
| 239 |
-
show_error("Error in cancel_function() in export_tool.", e)
|
| 240 |
-
|
| 241 |
-
# browse for folder function
|
| 242 |
-
# allows user to browse for a folder where to store the CSV file
|
| 243 |
-
def browseForFolder(self):
|
| 244 |
-
try:
|
| 245 |
-
# get the folder
|
| 246 |
-
filed = QtWidgets.QFileDialog()
|
| 247 |
-
mydir = QtWidgets.QFileDialog.getExistingDirectory(filed, "Open a Folder",
|
| 248 |
-
GlobalSettings.CSPR_DB, QtWidgets.QFileDialog.ShowDirsOnly)
|
| 249 |
-
if(os.path.isdir(mydir) == False):
|
| 250 |
-
return
|
| 251 |
-
|
| 252 |
-
if platform.system() == "Windows":
|
| 253 |
-
self.fileLocation_line_edit.setText(mydir + "\\")
|
| 254 |
-
self.location = mydir + "\\"
|
| 255 |
-
else:
|
| 256 |
-
self.fileLocation_line_edit.setText(mydir + "/")
|
| 257 |
-
self.location = mydir + "/"
|
| 258 |
-
except Exception as e:
|
| 259 |
-
show_error("Error in browseForFolder() in export_tool.", e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,662 +0,0 @@
|
|
| 1 |
-
import models.GlobalSettings as GlobalSettings
|
| 2 |
-
import os
|
| 3 |
-
from PyQt5 import QtWidgets, Qt, uic, QtCore
|
| 4 |
-
from functools import partial
|
| 5 |
-
from models.CSPRparser import CSPRparser
|
| 6 |
-
import re
|
| 7 |
-
import platform
|
| 8 |
-
import traceback
|
| 9 |
-
import math
|
| 10 |
-
from utils.ui import show_message, show_error, scale_ui, center_ui
|
| 11 |
-
from views.annotation_functions import *
|
| 12 |
-
|
| 13 |
-
logger = GlobalSettings.logger
|
| 14 |
-
|
| 15 |
-
# this class is a window that allows the user to select the settings for Generate Library
|
| 16 |
-
# When the user clicks Generate Library, it goes ahead and gets the Annotation Data needed
|
| 17 |
-
# Then the user can select the settings they want, and then hit submit.
|
| 18 |
-
# It creates a txt file with the data
|
| 19 |
-
class genLibrary(QtWidgets.QMainWindow):
|
| 20 |
-
def __init__(self):
|
| 21 |
-
try:
|
| 22 |
-
super(genLibrary, self).__init__()
|
| 23 |
-
uic.loadUi(GlobalSettings.appdir + 'ui/generate_library.ui', self)
|
| 24 |
-
self.setWindowTitle('Generate Library')
|
| 25 |
-
self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + 'cas9image.ico'))
|
| 26 |
-
|
| 27 |
-
groupbox_style = """
|
| 28 |
-
QGroupBox:title{subcontrol-origin: margin;
|
| 29 |
-
left: 10px;
|
| 30 |
-
padding: 0 5px 0 5px;}
|
| 31 |
-
QGroupBox#Step1{border: 2px solid rgb(111,181,110);
|
| 32 |
-
border-radius: 9px;
|
| 33 |
-
font: bold 14pt 'Arial';
|
| 34 |
-
margin-top: 10px;}"""
|
| 35 |
-
self.Step1.setStyleSheet(groupbox_style)
|
| 36 |
-
self.Step2.setStyleSheet(groupbox_style.replace("Step1", "Step2"))
|
| 37 |
-
self.Step3.setStyleSheet(groupbox_style.replace("Step1", "Step3"))
|
| 38 |
-
self.Step4.setStyleSheet(groupbox_style.replace("Step1", "Step4"))
|
| 39 |
-
|
| 40 |
-
self.cancel_button.clicked.connect(self.cancel_function)
|
| 41 |
-
self.BrowseButton.clicked.connect(self.browse_function)
|
| 42 |
-
self.submit_button.clicked.connect(self.submit_data)
|
| 43 |
-
self.progressBar.setValue(0)
|
| 44 |
-
|
| 45 |
-
self.anno_data = dict()
|
| 46 |
-
self.kegg_nonKegg = ''
|
| 47 |
-
self.gen_lib_dict = dict()
|
| 48 |
-
self.cspr_data = dict()
|
| 49 |
-
self.Output = dict()
|
| 50 |
-
self.off_tol = .05
|
| 51 |
-
self.off_max_misMatch = 4
|
| 52 |
-
self.off_target_running = False
|
| 53 |
-
self.parser = CSPRparser("")
|
| 54 |
-
|
| 55 |
-
# set the numbers for the num genes combo box item
|
| 56 |
-
for i in range(10):
|
| 57 |
-
self.numGenescomboBox.addItem(str(i + 1))
|
| 58 |
-
|
| 59 |
-
# set the numbers for the minOn combo box
|
| 60 |
-
for i in range(19, 70):
|
| 61 |
-
self.minON_comboBox.addItem(str(i + 1))
|
| 62 |
-
|
| 63 |
-
scale_ui(self, custom_scale_width=950, custom_scale_height=500)
|
| 64 |
-
|
| 65 |
-
except Exception as e:
|
| 66 |
-
show_error("Error initializing generate library class.", e)
|
| 67 |
-
|
| 68 |
-
# this function launches the window
|
| 69 |
-
# Parameters:
|
| 70 |
-
# annotation_data: a dictionary that has the data for the annotations searched for
|
| 71 |
-
# currently MainWindow's searches dict is passed into this
|
| 72 |
-
# org_file: the cspr_file that pertains to the organism that user is using at the time
|
| 73 |
-
# anno_type: whether the user is using KEGG or another type of annotation file
|
| 74 |
-
def launch(self, annotation_data, org_file, anno_type):
|
| 75 |
-
try:
|
| 76 |
-
self.cspr_file = org_file
|
| 77 |
-
self.db_file = org_file[:org_file.find('.')] + '_repeats.db'
|
| 78 |
-
self.anno_data = annotation_data
|
| 79 |
-
self.kegg_nonKegg = anno_type
|
| 80 |
-
self.process = QtCore.QProcess()
|
| 81 |
-
self.parser.fileName = org_file
|
| 82 |
-
|
| 83 |
-
# setting the path and file name fields
|
| 84 |
-
index1 = self.cspr_file.find('.')
|
| 85 |
-
if platform.system() == "Windows":
|
| 86 |
-
index2 = self.cspr_file.rfind('\\')
|
| 87 |
-
else:
|
| 88 |
-
index2 = self.cspr_file.rfind('/')
|
| 89 |
-
|
| 90 |
-
self.filename_input.setText(self.cspr_file[index2 + 1:index1] + '_lib')
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
if platform.system() == "Windows":
|
| 94 |
-
self.output_path.setText(GlobalSettings.CSPR_DB + "\\")
|
| 95 |
-
else:
|
| 96 |
-
self.output_path.setText(GlobalSettings.CSPR_DB + "/")
|
| 97 |
-
|
| 98 |
-
# depending on the type of file, build the dictionary accordingly
|
| 99 |
-
self.build_dict_non_kegg()
|
| 100 |
-
|
| 101 |
-
# get the gRNA data from the cspr file
|
| 102 |
-
self.cspr_data = self.parser.gen_lib_parser(self.gen_lib_dict, GlobalSettings.mainWindow.endoChoice.currentText())
|
| 103 |
-
self.get_endo_data()
|
| 104 |
-
|
| 105 |
-
center_ui(self)
|
| 106 |
-
self.show()
|
| 107 |
-
self.activateWindow()
|
| 108 |
-
except Exception as e:
|
| 109 |
-
show_error("Error in launch() in generate library.", e)
|
| 110 |
-
|
| 111 |
-
def get_endo_data(self):
|
| 112 |
-
try:
|
| 113 |
-
f = open(GlobalSettings.appdir + "CASPERinfo")
|
| 114 |
-
self.endo_data = {}
|
| 115 |
-
while True:
|
| 116 |
-
line = f.readline()
|
| 117 |
-
if line.startswith('ENDONUCLEASES'):
|
| 118 |
-
while True:
|
| 119 |
-
line = f.readline()
|
| 120 |
-
line = line.replace("\n","")
|
| 121 |
-
if (line[0] == "-"):
|
| 122 |
-
break
|
| 123 |
-
line_tokened = line.split(";")
|
| 124 |
-
if len(line_tokened) == 10:
|
| 125 |
-
endo = line_tokened[0]
|
| 126 |
-
five_length = line_tokened[2]
|
| 127 |
-
seed_length = line_tokened[3]
|
| 128 |
-
three_length = line_tokened[4]
|
| 129 |
-
prime = line_tokened[5]
|
| 130 |
-
hsu = line_tokened[9]
|
| 131 |
-
self.endo_data[endo] = [int(five_length) + int(three_length) + int(seed_length), prime, "MATRIX:" + hsu]
|
| 132 |
-
|
| 133 |
-
break
|
| 134 |
-
f.close()
|
| 135 |
-
except Exception as e:
|
| 136 |
-
show_error("Error in get_endo_data() in generate library.", e)
|
| 137 |
-
|
| 138 |
-
# this is here in case the user clicks 'x' instead of cancel. Just calls the cancel function
|
| 139 |
-
def closeEvent(self, event):
|
| 140 |
-
try:
|
| 141 |
-
closeWindow = self.cancel_function()
|
| 142 |
-
|
| 143 |
-
# if the user is doing OT and does not decide to cancel it ignore the event
|
| 144 |
-
if closeWindow == -2:
|
| 145 |
-
event.ignore()
|
| 146 |
-
else:
|
| 147 |
-
event.accept()
|
| 148 |
-
except Exception as e:
|
| 149 |
-
show_error("Error in closeEvent() in generate library.", e)
|
| 150 |
-
|
| 151 |
-
# this function takes all of the cspr data and compresses it again for off-target usage
|
| 152 |
-
def compress_file_off(self):
|
| 153 |
-
try:
|
| 154 |
-
if platform.system() == "Windows":
|
| 155 |
-
file = GlobalSettings.CSPR_DB + "\\off_input.txt"
|
| 156 |
-
else:
|
| 157 |
-
file = GlobalSettings.CSPR_DB + "/off_input.txt"
|
| 158 |
-
f = open(file, 'w')
|
| 159 |
-
for gene in self.cspr_data:
|
| 160 |
-
for j in range(len(self.cspr_data[gene])):
|
| 161 |
-
loc = self.cspr_data[gene][j][0]
|
| 162 |
-
seq = self.cspr_data[gene][j][1]
|
| 163 |
-
pam = self.cspr_data[gene][j][2]
|
| 164 |
-
score = self.cspr_data[gene][j][3]
|
| 165 |
-
strand = self.cspr_data[gene][j][4]
|
| 166 |
-
output = str(loc) + ';' + str(seq) + ';' + str(pam) + ';' + str(score) + ';' + str(strand)
|
| 167 |
-
f.write(output + '\n')
|
| 168 |
-
f.close()
|
| 169 |
-
except Exception as e:
|
| 170 |
-
show_error("Error in compress_file_off() in generate library.", e)
|
| 171 |
-
|
| 172 |
-
# this function parses the temp_off file, which holds the off-target analysis results
|
| 173 |
-
# it also updates each target in the cspr_data dictionary to replace the endo with the target's results in off-target
|
| 174 |
-
def parse_off_file(self):
|
| 175 |
-
try:
|
| 176 |
-
if platform.system() == "Windows":
|
| 177 |
-
file = GlobalSettings.CSPR_DB + "\\temp_off.txt"
|
| 178 |
-
else:
|
| 179 |
-
file = GlobalSettings.CSPR_DB + "/temp_off.txt"
|
| 180 |
-
f = open(file, "r")
|
| 181 |
-
file_data = f.read().split('\n')
|
| 182 |
-
f.close()
|
| 183 |
-
scoreDict = dict()
|
| 184 |
-
|
| 185 |
-
# get the data from the file
|
| 186 |
-
for i in range(len(file_data)):
|
| 187 |
-
if file_data[i] == 'AVG OUTPUT':
|
| 188 |
-
continue
|
| 189 |
-
elif file_data[i] != '':
|
| 190 |
-
buffer = file_data[i].split(':')
|
| 191 |
-
scoreDict[buffer[0]] = buffer[1]
|
| 192 |
-
|
| 193 |
-
# update cspr_Data
|
| 194 |
-
for gene in self.cspr_data:
|
| 195 |
-
for i in range(len(self.cspr_data[gene])):
|
| 196 |
-
tempTuple = (self.cspr_data[gene][i][0], self.cspr_data[gene][i][1], self.cspr_data[gene][i][2], self.cspr_data[gene][i][3], self.cspr_data[gene][i][4], scoreDict[self.cspr_data[gene][i][1]])
|
| 197 |
-
self.cspr_data[gene][i] = tempTuple
|
| 198 |
-
except Exception as e:
|
| 199 |
-
show_error("Error in parse_off_file() in generate library.", e)
|
| 200 |
-
|
| 201 |
-
# this function runs the off_target command
|
| 202 |
-
# NOTE: some changes may be needed to get it to work with other OS besides windows
|
| 203 |
-
def get_offTarget_data(self, num_targets, minScore, spaceValue, output_file, fiveseq):
|
| 204 |
-
try:
|
| 205 |
-
self.perc = False
|
| 206 |
-
self.bool_temp = False
|
| 207 |
-
self.running = False
|
| 208 |
-
|
| 209 |
-
# when finished, parse the off file, and then generate the lib
|
| 210 |
-
def finished():
|
| 211 |
-
if self.off_target_running:
|
| 212 |
-
self.progressBar.setValue(100)
|
| 213 |
-
self.parse_off_file()
|
| 214 |
-
did_work = self.generate(num_targets, minScore, spaceValue, output_file, fiveseq)
|
| 215 |
-
self.off_target_running = False
|
| 216 |
-
#self.process.kill()
|
| 217 |
-
if did_work != -1:
|
| 218 |
-
self.cancel_function()
|
| 219 |
-
show_message(
|
| 220 |
-
fontSize=12,
|
| 221 |
-
icon=QtWidgets.QMessageBox.Icon.Information,
|
| 222 |
-
title="Library Generated!",
|
| 223 |
-
message="CASPER has finished generating your library!"
|
| 224 |
-
)
|
| 225 |
-
os.remove(GlobalSettings.CSPR_DB + '/off_input.txt')
|
| 226 |
-
os.remove(GlobalSettings.CSPR_DB + '/temp_off.txt')
|
| 227 |
-
|
| 228 |
-
# as off-targeting outputs things, update the off-target progress bar
|
| 229 |
-
def progUpdate(p):
|
| 230 |
-
line = str(self.process.readAllStandardOutput())
|
| 231 |
-
line = line[2:]
|
| 232 |
-
line = line[:len(line) - 1]
|
| 233 |
-
if platform.system() == 'Windows':
|
| 234 |
-
for lines in filter(None, line.split(r'\r\n')):
|
| 235 |
-
if (lines.find("Running Off Target Algorithm for") != -1 and self.perc == False):
|
| 236 |
-
self.perc = True
|
| 237 |
-
if (self.perc == True and self.bool_temp == False and lines.find(
|
| 238 |
-
"Running Off Target Algorithm for") == -1):
|
| 239 |
-
lines = lines[32:]
|
| 240 |
-
lines = lines.replace("%", "")
|
| 241 |
-
if (float(lines) <= 99.5):
|
| 242 |
-
num = float(lines)
|
| 243 |
-
self.progressBar.setValue(num)
|
| 244 |
-
else:
|
| 245 |
-
self.bool_temp = True
|
| 246 |
-
else:
|
| 247 |
-
for lines in filter(None, line.split(r'\n')):
|
| 248 |
-
if (lines.find("Running Off Target Algorithm for") != -1 and self.perc == False):
|
| 249 |
-
self.perc = True
|
| 250 |
-
if (self.perc == True and self.bool_temp == False and lines.find(
|
| 251 |
-
"Running Off Target Algorithm for") == -1):
|
| 252 |
-
lines = lines[32:]
|
| 253 |
-
lines = lines.replace("%", "")
|
| 254 |
-
if (float(lines) <= 99.5):
|
| 255 |
-
num = float(lines)
|
| 256 |
-
self.progressBar.setValue(num)
|
| 257 |
-
else:
|
| 258 |
-
self.bool_temp = True
|
| 259 |
-
|
| 260 |
-
if platform.system() == 'Windows':
|
| 261 |
-
app_path = GlobalSettings.appdir
|
| 262 |
-
exe_path = app_path + 'OffTargetFolder\\OT_Win.exe'
|
| 263 |
-
output_path = '"' + GlobalSettings.CSPR_DB + '\\temp_off.txt" '
|
| 264 |
-
data_path = '"' + GlobalSettings.CSPR_DB + "\\off_input.txt" + '" '
|
| 265 |
-
elif platform.system() == 'Linux':
|
| 266 |
-
app_path = GlobalSettings.appdir.replace('\\', '/')
|
| 267 |
-
exe_path = app_path + r'OffTargetFolder/OT_Lin'
|
| 268 |
-
output_path = '"' + GlobalSettings.CSPR_DB + '/temp_off.txt" '
|
| 269 |
-
data_path = '"' + GlobalSettings.CSPR_DB + "/off_input.txt" + '" '
|
| 270 |
-
else:
|
| 271 |
-
app_path = GlobalSettings.appdir.replace('\\', '/')
|
| 272 |
-
exe_path = app_path + r'OffTargetFolder/OT_Mac'
|
| 273 |
-
output_path = '"' + GlobalSettings.CSPR_DB + '/temp_off.txt" '
|
| 274 |
-
data_path = '"' + GlobalSettings.CSPR_DB + "/off_input.txt" + '" '
|
| 275 |
-
exe_path = '"' + exe_path + '" '
|
| 276 |
-
cspr_path = '"' + self.cspr_file + '" '
|
| 277 |
-
db_path = '"' + self.db_file + '" '
|
| 278 |
-
filename = output_path
|
| 279 |
-
filename = filename[:len(filename) - 1]
|
| 280 |
-
filename = filename[1:]
|
| 281 |
-
filename = filename.replace('"', '')
|
| 282 |
-
CASPER_info_path = '"' + app_path + 'CASPERinfo' +'" '
|
| 283 |
-
num_of_mismathes = self.off_max_misMatch
|
| 284 |
-
tolerance = self.off_tol # create command string
|
| 285 |
-
endo = '"' + GlobalSettings.mainWindow.endoChoice.currentText() + '" '
|
| 286 |
-
detailed_output = " False "
|
| 287 |
-
avg_output = "True"
|
| 288 |
-
hsu = ' "' + self.endo_data[GlobalSettings.mainWindow.endoChoice.currentText()][2] + '"'
|
| 289 |
-
|
| 290 |
-
# set the off_target_running to true, to keep the user from closing the window while it is running
|
| 291 |
-
self.off_target_running = True
|
| 292 |
-
|
| 293 |
-
cmd = exe_path + data_path + endo + cspr_path + db_path + output_path + CASPER_info_path + str(
|
| 294 |
-
num_of_mismathes) + ' ' + str(tolerance) + detailed_output + avg_output + hsu
|
| 295 |
-
|
| 296 |
-
if platform.system() == 'Windows':
|
| 297 |
-
cmd = cmd.replace('/', '\\')
|
| 298 |
-
self.process.readyReadStandardOutput.connect(partial(progUpdate, self.process))
|
| 299 |
-
self.process.readyReadStandardError.connect(partial(progUpdate, self.process))
|
| 300 |
-
self.progressBar.setValue(0)
|
| 301 |
-
QtCore.QTimer.singleShot(100, partial(self.process.start, cmd))
|
| 302 |
-
self.process.finished.connect(finished)
|
| 303 |
-
except Exception as e:
|
| 304 |
-
show_error("Error in get_offTarget_data() in generate library.", e)
|
| 305 |
-
|
| 306 |
-
# submit function
|
| 307 |
-
# this function takes all of the input from the window, and calls the generate function
|
| 308 |
-
# Still need to add the checks for 5' seq, and the percentage thing
|
| 309 |
-
def submit_data(self):
|
| 310 |
-
try:
|
| 311 |
-
if self.off_target_running:
|
| 312 |
-
return
|
| 313 |
-
output_file = self.output_path.text() + self.filename_input.text()
|
| 314 |
-
|
| 315 |
-
minScore = int(self.minON_comboBox.currentText())
|
| 316 |
-
num_targets = int(self.numGenescomboBox.currentText())
|
| 317 |
-
fiveseq = ''
|
| 318 |
-
|
| 319 |
-
# error check for csv files
|
| 320 |
-
if output_file.endswith('.txt'):
|
| 321 |
-
output_file = output_file.replace('.txt', '.csv')
|
| 322 |
-
elif not output_file.endswith('.txt') and not output_file.endswith('.csv'):
|
| 323 |
-
output_file = output_file + '.csv'
|
| 324 |
-
|
| 325 |
-
# error checking for the space value
|
| 326 |
-
# if they enter nothing, default to 15 and also make sure it's actually a digit
|
| 327 |
-
if self.space_line_edit.text() == '':
|
| 328 |
-
spaceValue = 15
|
| 329 |
-
elif self.space_line_edit.text().isdigit():
|
| 330 |
-
spaceValue = int(self.space_line_edit.text())
|
| 331 |
-
elif not self.space_line_edit.text().isdigit():
|
| 332 |
-
show_message(
|
| 333 |
-
fontSize=12,
|
| 334 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 335 |
-
title="Error",
|
| 336 |
-
message="Please enter integers only for space between guides."
|
| 337 |
-
)
|
| 338 |
-
return
|
| 339 |
-
# if space value is more than 200, default to 200
|
| 340 |
-
if spaceValue > 200:
|
| 341 |
-
spaceValue = 200
|
| 342 |
-
elif spaceValue < 0:
|
| 343 |
-
show_message(
|
| 344 |
-
fontSize=12,
|
| 345 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 346 |
-
title="Error",
|
| 347 |
-
message="Please enter a space-value that is 0 or greater."
|
| 348 |
-
)
|
| 349 |
-
return
|
| 350 |
-
|
| 351 |
-
if self.find_off_Checkbox.isChecked():
|
| 352 |
-
self.compress_file_off()
|
| 353 |
-
|
| 354 |
-
# get the fiveprimseq data and error check it
|
| 355 |
-
if self.fiveprimeseq.text() != '' and self.fiveprimeseq.text().isalpha():
|
| 356 |
-
fiveseq = self.fiveprimeseq.text()
|
| 357 |
-
elif self.fiveprimeseq.text() != '' and not self.fiveprimeseq.text().isalpha():
|
| 358 |
-
show_message(
|
| 359 |
-
fontSize=12,
|
| 360 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 361 |
-
title="Error",
|
| 362 |
-
message="Please make sure only the letters A, T, G, or C are added into 5' End specificity box."
|
| 363 |
-
)
|
| 364 |
-
return
|
| 365 |
-
|
| 366 |
-
# get the targeting range data, and error check it here
|
| 367 |
-
if not self.start_target_range.text().isdigit() or not self.end_target_range.text().isdigit():
|
| 368 |
-
show_message(
|
| 369 |
-
fontSize=12,
|
| 370 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 371 |
-
title="Error",
|
| 372 |
-
message="Error: Please make sure that the start and end target ranges are numbers only. Please make sure that start is 0 or greater, and end is 100 or less. "
|
| 373 |
-
)
|
| 374 |
-
return
|
| 375 |
-
elif int(self.start_target_range.text()) >= int(self.end_target_range.text()):
|
| 376 |
-
show_message(
|
| 377 |
-
fontSize=12,
|
| 378 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 379 |
-
title="Error",
|
| 380 |
-
message="Please make sure that the start number is always less than the end number"
|
| 381 |
-
)
|
| 382 |
-
return
|
| 383 |
-
|
| 384 |
-
# if they check Off-Targeting
|
| 385 |
-
if self.find_off_Checkbox.isChecked():
|
| 386 |
-
# make sure its a digit
|
| 387 |
-
if self.maxOFF_comboBox.text() == '' or not self.maxOFF_comboBox.text().isdigit() and '.' not in self.maxOFF_comboBox.text():
|
| 388 |
-
show_message(
|
| 389 |
-
fontSize=12,
|
| 390 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 391 |
-
title="Error",
|
| 392 |
-
message="Please enter only numbers for Maximum Off-Target Score. It cannot be left blank"
|
| 393 |
-
)
|
| 394 |
-
return
|
| 395 |
-
else:
|
| 396 |
-
# make sure it between 0 and .5
|
| 397 |
-
if not 0.0 < float(self.maxOFF_comboBox.text()) <= .5:
|
| 398 |
-
show_message(
|
| 399 |
-
fontSize=12,
|
| 400 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 401 |
-
title="Error",
|
| 402 |
-
message="Please enter a max off-target score between 0 and 0.5!"
|
| 403 |
-
)
|
| 404 |
-
return
|
| 405 |
-
# compress the data, and then run off-targeting
|
| 406 |
-
self.compress_file_off()
|
| 407 |
-
self.get_offTarget_data(num_targets, minScore, spaceValue, output_file, fiveseq)
|
| 408 |
-
else:
|
| 409 |
-
# actually call the generate function
|
| 410 |
-
did_work = self.generate(num_targets, minScore, spaceValue, output_file, fiveseq)
|
| 411 |
-
|
| 412 |
-
if did_work != -1:
|
| 413 |
-
self.cancel_function()
|
| 414 |
-
show_message(
|
| 415 |
-
fontSize=12,
|
| 416 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 417 |
-
title="Library Generated!",
|
| 418 |
-
message="CASPER has finished generating your library!"
|
| 419 |
-
)
|
| 420 |
-
except Exception as e:
|
| 421 |
-
show_error("Error in submit_data() in generate library.", e)
|
| 422 |
-
|
| 423 |
-
# clears everything and hides the window
|
| 424 |
-
def cancel_function(self):
|
| 425 |
-
try:
|
| 426 |
-
if self.off_target_running:
|
| 427 |
-
msgBox = QtWidgets.QMessageBox()
|
| 428 |
-
msgBox.setStyleSheet("font: " + str(12) + "pt 'Arial'")
|
| 429 |
-
msgBox.setIcon(QtWidgets.QMessageBox.Icon.Question)
|
| 430 |
-
msgBox.setWindowTitle("Off-Targeting is running")
|
| 431 |
-
msgBox.setText(
|
| 432 |
-
"Off-Targetting is running. Closing this window will cancel that process, and return to the main window. .\n Do you wish to continue?")
|
| 433 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.Yes)
|
| 434 |
-
msgBox.addButton(QtWidgets.QMessageBox.StandardButton.No)
|
| 435 |
-
msgBox.exec()
|
| 436 |
-
|
| 437 |
-
if (msgBox.result() == QtWidgets.QMessageBox.No):
|
| 438 |
-
return -2
|
| 439 |
-
else:
|
| 440 |
-
self.off_target_running = False
|
| 441 |
-
self.process.kill()
|
| 442 |
-
|
| 443 |
-
self.cspr_file = ''
|
| 444 |
-
self.anno_data = list()
|
| 445 |
-
|
| 446 |
-
self.filename_input.setText('')
|
| 447 |
-
self.output_path.setText('')
|
| 448 |
-
|
| 449 |
-
self.gen_lib_dict.clear()
|
| 450 |
-
self.cspr_data.clear()
|
| 451 |
-
self.Output.clear()
|
| 452 |
-
|
| 453 |
-
self.start_target_range.setText('0')
|
| 454 |
-
self.end_target_range.setText('100')
|
| 455 |
-
self.space_line_edit.setText('15')
|
| 456 |
-
self.find_off_Checkbox.setChecked(False)
|
| 457 |
-
self.modifyParamscheckBox.setChecked(False)
|
| 458 |
-
self.maxOFF_comboBox.setText('')
|
| 459 |
-
self.fiveprimeseq.setText('')
|
| 460 |
-
self.off_target_running = False
|
| 461 |
-
self.progressBar.setValue(0)
|
| 462 |
-
|
| 463 |
-
self.hide()
|
| 464 |
-
except Exception as e:
|
| 465 |
-
show_error("Error in cancel_function() in generate library.", e)
|
| 466 |
-
|
| 467 |
-
# allows the user to browse for a folder
|
| 468 |
-
# stores their selection in the output_path line edit
|
| 469 |
-
def browse_function(self):
|
| 470 |
-
try:
|
| 471 |
-
if self.off_target_running:
|
| 472 |
-
return
|
| 473 |
-
# get the folder
|
| 474 |
-
filed = QtWidgets.QFileDialog()
|
| 475 |
-
mydir = QtWidgets.QFileDialog.getExistingDirectory(filed, "Open a Folder",
|
| 476 |
-
GlobalSettings.CSPR_DB, QtWidgets.QFileDialog.ShowDirsOnly)
|
| 477 |
-
if(os.path.isdir(mydir) == False):
|
| 478 |
-
return
|
| 479 |
-
|
| 480 |
-
# make sure to append the '/' to the folder path
|
| 481 |
-
if platform.system() == "Windwos":
|
| 482 |
-
self.output_path.setText(mydir + "\\")
|
| 483 |
-
else:
|
| 484 |
-
self.output_path.setText(mydir + "/")
|
| 485 |
-
except Exception as e:
|
| 486 |
-
show_error("Error in browse_function() in generate library.", e)
|
| 487 |
-
|
| 488 |
-
# this function builds the dictionary that is used in the generate function
|
| 489 |
-
# this is the version that builds it from data from feature_table, gbff, or gff
|
| 490 |
-
# builds it exactly as Brian built it in the files given
|
| 491 |
-
def build_dict_non_kegg(self):
|
| 492 |
-
try:
|
| 493 |
-
for tuple in self.anno_data:
|
| 494 |
-
chrom = tuple[0]
|
| 495 |
-
feature = tuple[1]
|
| 496 |
-
feature_id = get_id(feature)
|
| 497 |
-
feature_name = get_name(feature)
|
| 498 |
-
feature_desc = get_description(feature)
|
| 499 |
-
### Order: chromosome number, gene start, gene end, dir of gene, gene description, gene name/locus tag
|
| 500 |
-
self.gen_lib_dict[feature_name] = [chrom,int(feature.location.start),int(feature.location.end),get_strand(feature),get_description(feature),get_name(feature)]
|
| 501 |
-
except Exception as e:
|
| 502 |
-
show_error("Error in build_dict_non_kegg() in generate library.", e)
|
| 503 |
-
|
| 504 |
-
# generate function taken from Brian's code
|
| 505 |
-
def generate(self,num_targets_per_gene, score_limit, space, output_file, fiveseq):
|
| 506 |
-
try:
|
| 507 |
-
deletedDict = dict()
|
| 508 |
-
|
| 509 |
-
# check and see if we need to search based on target_range
|
| 510 |
-
startNum = float(self.start_target_range.text())
|
| 511 |
-
endNum = float(self.end_target_range.text())
|
| 512 |
-
checkStartandEndBool = False
|
| 513 |
-
if startNum != 0.0 or endNum != 100.0:
|
| 514 |
-
if startNum >= 0.0 and endNum <= 100.0:
|
| 515 |
-
startNum = startNum / 100
|
| 516 |
-
endNum = endNum / 100
|
| 517 |
-
checkStartandEndBool = True
|
| 518 |
-
else:
|
| 519 |
-
show_message(
|
| 520 |
-
fontSize=12,
|
| 521 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 522 |
-
title="Invalid Targeting Range:",
|
| 523 |
-
message="Please select a targeting range between 0 and 100."
|
| 524 |
-
)
|
| 525 |
-
return -1
|
| 526 |
-
|
| 527 |
-
for gene in self.gen_lib_dict:
|
| 528 |
-
target_list = self.cspr_data[gene] # Gets the gRNAs for given gene
|
| 529 |
-
|
| 530 |
-
#target_list = chrom_list[k:l+1]
|
| 531 |
-
# Reverse the target list if the gene is on negative strand:
|
| 532 |
-
if self.gen_lib_dict[gene][3] == "-":
|
| 533 |
-
target_list.reverse()
|
| 534 |
-
|
| 535 |
-
# Filter out the guides with low scores and long strings of T's
|
| 536 |
-
# also store the ones deleted if the user selects 'modify search parameters'
|
| 537 |
-
if self.modifyParamscheckBox.isChecked():
|
| 538 |
-
deletedDict[gene] = list()
|
| 539 |
-
for i in range(len(target_list) - 1, -1, -1): ### Start at end and move backwards through list
|
| 540 |
-
# check the target_range here
|
| 541 |
-
if int(target_list[i][3]) < int(score_limit):
|
| 542 |
-
if self.modifyParamscheckBox.isChecked():
|
| 543 |
-
deletedDict[gene].append(target_list[i])
|
| 544 |
-
target_list.pop(i)
|
| 545 |
-
# check for gRNAs with poly T regions here
|
| 546 |
-
elif re.search("T{5,10}", target_list[i][1]) is not None:
|
| 547 |
-
if self.modifyParamscheckBox.isChecked():
|
| 548 |
-
deletedDict[gene].append(target_list[i])
|
| 549 |
-
target_list.pop(i)
|
| 550 |
-
|
| 551 |
-
# check for the fiveseq
|
| 552 |
-
if fiveseq != '':
|
| 553 |
-
for i in range(len(target_list) - 1, -1, -1): ### Start at end and move backwards through list
|
| 554 |
-
if not target_list[i][1].startswith(fiveseq.upper()):
|
| 555 |
-
if self.modifyParamscheckBox.isChecked():
|
| 556 |
-
deletedDict[gene].append(target_list[i])
|
| 557 |
-
target_list.pop(i)
|
| 558 |
-
# check the target range here
|
| 559 |
-
if checkStartandEndBool:
|
| 560 |
-
for i in range(len(target_list) - 1, -1, -1):
|
| 561 |
-
totalDistance = self.gen_lib_dict[gene][2] - self.gen_lib_dict[gene][1]
|
| 562 |
-
target_loc = abs(int(target_list[i][0])) - int(self.gen_lib_dict[gene][1])
|
| 563 |
-
myRatio = target_loc / totalDistance
|
| 564 |
-
|
| 565 |
-
if not (startNum <= myRatio <= endNum):
|
| 566 |
-
if self.modifyParamscheckBox.isChecked():
|
| 567 |
-
deletedDict[gene].append(target_list[i])
|
| 568 |
-
target_list.pop(i)
|
| 569 |
-
# if the user selected off-targeting, check to see that the targets do not exceed the selected max score
|
| 570 |
-
if self.find_off_Checkbox.isChecked():
|
| 571 |
-
maxScore = float(self.maxOFF_comboBox.text())
|
| 572 |
-
for i in range(len(target_list) - 1, -1, -1):
|
| 573 |
-
if maxScore < float(target_list[i][5]):
|
| 574 |
-
if self.modifyParamscheckBox.isChecked():
|
| 575 |
-
deletedDict[gene].append(target_list[i])
|
| 576 |
-
target_list.pop(i)
|
| 577 |
-
# Now generating the targets
|
| 578 |
-
self.Output[gene] = list()
|
| 579 |
-
i = 0
|
| 580 |
-
vec_index = 0
|
| 581 |
-
prev_target = (0, "xyz", 'abc', 1, "-")
|
| 582 |
-
while i < num_targets_per_gene:
|
| 583 |
-
# select the first five targets with the score and space filter that is set in the beginning
|
| 584 |
-
if len(target_list) == 0 or vec_index >= len(target_list):
|
| 585 |
-
break
|
| 586 |
-
while abs(int(target_list[vec_index][0]) - int(prev_target[0])) < int(space):
|
| 587 |
-
if target_list[vec_index][3] > prev_target[3] and prev_target != (0,"xyz", "abc", 1, "-"):
|
| 588 |
-
self.Output[gene].remove(prev_target)
|
| 589 |
-
self.Output[gene].append(target_list[vec_index])
|
| 590 |
-
prev_target = target_list[vec_index]
|
| 591 |
-
vec_index += 1
|
| 592 |
-
# check and see if there will be a indexing error
|
| 593 |
-
if vec_index >= len(target_list) - 1:
|
| 594 |
-
vec_index = vec_index - 1
|
| 595 |
-
break
|
| 596 |
-
# Add the new target to the output and add another to i
|
| 597 |
-
self.Output[gene].append(target_list[vec_index])
|
| 598 |
-
prev_target = target_list[vec_index]
|
| 599 |
-
i += 1
|
| 600 |
-
vec_index += 1
|
| 601 |
-
|
| 602 |
-
# if the user selects modify search parameters, go through and check to see if each one has the number of targets that the user wanted
|
| 603 |
-
# if not, append from the deletedDict until they do
|
| 604 |
-
if self.modifyParamscheckBox.isChecked():
|
| 605 |
-
for gene in self.Output:
|
| 606 |
-
if len(self.Output[gene]) < num_targets_per_gene:
|
| 607 |
-
for i in range(len(deletedDict[gene])):
|
| 608 |
-
if len(self.Output[gene]) == num_targets_per_gene:
|
| 609 |
-
break
|
| 610 |
-
else:
|
| 611 |
-
loc = deletedDict[gene][i][0]
|
| 612 |
-
seq = deletedDict[gene][i][1]
|
| 613 |
-
pam = deletedDict[gene][i][2]
|
| 614 |
-
score = deletedDict[gene][i][3]
|
| 615 |
-
strand = deletedDict[gene][i][4] + '*'
|
| 616 |
-
endo = deletedDict[gene][i][5]
|
| 617 |
-
self.Output[gene].append((loc, seq, pam, score, strand, endo))
|
| 618 |
-
|
| 619 |
-
# Now output to the file
|
| 620 |
-
try:
|
| 621 |
-
f = open(output_file, 'w')
|
| 622 |
-
# if OT checked
|
| 623 |
-
if self.find_off_Checkbox.isChecked():
|
| 624 |
-
f.write('Gene Name,Sequence,On-Target Score,Off-Target Score,Location,PAM,Strand\n')
|
| 625 |
-
elif not self.find_off_Checkbox.isChecked():
|
| 626 |
-
f.write('Gene Name,Sequence,On-Target Score,Location,PAM,Strand\n')
|
| 627 |
-
|
| 628 |
-
for gene in self.Output:
|
| 629 |
-
i = 0
|
| 630 |
-
gene_name = self.gen_lib_dict[gene][-1]
|
| 631 |
-
for target in self.Output[gene]:
|
| 632 |
-
# check to see if the target did not match the user's parameters and they selected 'modify'
|
| 633 |
-
# if the target has an error, put 2 asterisks in front of the target sequence
|
| 634 |
-
if '*' in target[4]:
|
| 635 |
-
tag_id = "**" + gene_name + "-" + str(i + 1)
|
| 636 |
-
else:
|
| 637 |
-
tag_id = gene_name + "-" + str(i + 1)
|
| 638 |
-
i += 1
|
| 639 |
-
|
| 640 |
-
tag_id = tag_id.replace(',', '')
|
| 641 |
-
|
| 642 |
-
# if OT checked
|
| 643 |
-
if self.find_off_Checkbox.isChecked():
|
| 644 |
-
f.write(tag_id + ',' + target[1] + ',' + str(target[3]) + ',' + str(target[5]) + ',' + str(abs(int(target[0]))) + ',' + target[2] + ',' + target[4][0] + '\n')
|
| 645 |
-
# if OT not checked
|
| 646 |
-
elif not self.find_off_Checkbox.isChecked():
|
| 647 |
-
f.write(tag_id + ',' + target[1] + ',' + str(target[3]) + ',' + str(abs(int(target[0]))) + ',' + target[2] + ',' + target[4][0] + '\n')
|
| 648 |
-
|
| 649 |
-
f.close()
|
| 650 |
-
except PermissionError:
|
| 651 |
-
show_message(
|
| 652 |
-
fontSize=12,
|
| 653 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 654 |
-
title="File Cannot Open",
|
| 655 |
-
message="This file cannot be opened. Please make sure that the file is not opened elsewhere and try again."
|
| 656 |
-
)
|
| 657 |
-
return -1
|
| 658 |
-
except Exception as e:
|
| 659 |
-
print(e)
|
| 660 |
-
return
|
| 661 |
-
except Exception as e:
|
| 662 |
-
show_error("Error in generate() in generate library.", e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|